Open nt4f04uNd opened 3 years ago
I'm not sure myself what the expected behaviour is. I believe they are a "hint" to the client on how to display things but the client is free to use that hint however it likes.
clearly, the documented behavior is that they will be preferred this is likely a bug in android/compat library
The relevant code is in buildNotification
:
MediaDescriptionCompat description = mediaMetadata.getDescription();
if (description.getTitle() != null)
builder.setContentTitle(description.getTitle());
And the getDescription
method itself:
(the exact behaviour of which is heuristic and undocumented.)
Which API doesn't behave as documented, and how does it misbehave? displayTitle and displaySubtitle
Minimal reproduction project
code sample
```dart import 'dart:async'; import 'dart:math'; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_tts/flutter_tts.dart'; import 'package:just_audio/just_audio.dart'; import 'package:rxdart/rxdart.dart'; // You might want to provide this using dependency injection rather than a // global variable. late AudioHandler _audioHandler; /// Extension methods for our custom actions. extension DemoAudioHandler on AudioHandler { Future?, MediaItem?, QueueState>( _audioHandler.queue, _audioHandler.mediaItem, (queue, mediaItem) => QueueState(queue, mediaItem)); ElevatedButton startButton(String label, VoidCallback onPressed) => ElevatedButton( child: Text(label), onPressed: onPressed, ); IconButton playButton() => IconButton( icon: Icon(Icons.play_arrow), iconSize: 64.0, onPressed: _audioHandler.play, ); IconButton pauseButton() => IconButton( icon: Icon(Icons.pause), iconSize: 64.0, onPressed: _audioHandler.pause, ); IconButton stopButton() => IconButton( icon: Icon(Icons.stop), iconSize: 64.0, onPressed: _audioHandler.stop, ); } class QueueState { final List? queue;
final MediaItem? mediaItem;
QueueState(this.queue, this.mediaItem);
}
class MediaState {
final MediaItem? mediaItem;
final Duration position;
MediaState(this.mediaItem, this.position);
}
class SeekBar extends StatefulWidget {
final Duration duration;
final Duration position;
final ValueChanged? onChanged;
final ValueChanged? onChangeEnd;
SeekBar({
required this.duration,
required this.position,
this.onChanged,
this.onChangeEnd,
});
@override
_SeekBarState createState() => _SeekBarState();
}
class _SeekBarState extends State {
double? _dragValue;
bool _dragging = false;
@override
Widget build(BuildContext context) {
final value = min(
_dragValue ?? widget.position.inMilliseconds.toDouble(),
widget.duration.inMilliseconds.toDouble(),
);
if (_dragValue != null && !_dragging) {
_dragValue = null;
}
return Stack(
children: [
Slider(
min: 0.0,
max: widget.duration.inMilliseconds.toDouble(),
value: value,
onChanged: (value) {
if (!_dragging) {
_dragging = true;
}
setState(() {
_dragValue = value;
});
if (widget.onChanged != null) {
widget.onChanged!(Duration(milliseconds: value.round()));
}
},
onChangeEnd: (value) {
if (widget.onChangeEnd != null) {
widget.onChangeEnd!(Duration(milliseconds: value.round()));
}
_dragging = false;
},
),
Positioned(
right: 16.0,
bottom: 0.0,
child: Text(
RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
.firstMatch("$_remaining")
?.group(1) ??
'$_remaining',
style: Theme.of(context).textTheme.caption),
),
],
);
}
Duration get _remaining => widget.duration - widget.position;
}
class CustomEvent {
final int handlerIndex;
CustomEvent(this.handlerIndex);
}
class MainSwitchHandler extends SwitchAudioHandler {
final List handlers;
@override
BehaviorSubject customState = BehaviorSubject.seeded(CustomEvent(0));
MainSwitchHandler(this.handlers) : super(handlers.first) {
// Configure the app's audio category and attributes for speech.
AudioSession.instance.then((session) {
session.configure(AudioSessionConfiguration.speech());
});
}
@override
Future customAction(
String name, Map? extras) async {
switch (name) {
case 'switchToHandler':
stop();
final int index = extras!['index'];
inner = handlers[index];
customState.add(CustomEvent(index));
return null;
default:
return super.customAction(name, extras);
}
}
}
class LoggingAudioHandler extends CompositeAudioHandler {
LoggingAudioHandler(AudioHandler inner) : super(inner) {
playbackState.listen((state) {
_log('playbackState changed: $state');
});
queue.listen((queue) {
_log('queue changed: $queue');
});
queueTitle.listen((queueTitle) {
_log('queueTitle changed: $queueTitle');
});
mediaItem.listen((mediaItem) {
_log('mediaItem changed: $mediaItem');
});
ratingStyle.listen((ratingStyle) {
_log('ratingStyle changed: $ratingStyle');
});
androidPlaybackInfo.listen((androidPlaybackInfo) {
_log('androidPlaybackInfo changed: $androidPlaybackInfo');
});
customEvent.listen((customEventStream) {
_log('customEvent changed: $customEventStream');
});
customState.listen((customState) {
_log('customState changed: $customState');
});
}
// TODO: Use logger. Use different log levels.
void _log(String s) => print('----- LOG: $s');
@override
Future prepare() {
_log('prepare()');
return super.prepare();
}
@override
Future prepareFromMediaId(String mediaId,
[Map? extras]) {
_log('prepareFromMediaId($mediaId, $extras)');
return super.prepareFromMediaId(mediaId, extras);
}
@override
Future prepareFromSearch(String query, [Map? extras]) {
_log('prepareFromSearch($query, $extras)');
return super.prepareFromSearch(query, extras);
}
@override
Future prepareFromUri(Uri uri, [Map? extras]) {
_log('prepareFromSearch($uri, $extras)');
return super.prepareFromUri(uri, extras);
}
@override
Future play() {
_log('play()');
return super.play();
}
@override
Future playFromMediaId(String mediaId, [Map? extras]) {
_log('playFromMediaId($mediaId, $extras)');
return super.playFromMediaId(mediaId, extras);
}
@override
Future playFromSearch(String query, [Map? extras]) {
_log('playFromSearch($query, $extras)');
return super.playFromSearch(query, extras);
}
@override
Future playFromUri(Uri uri, [Map? extras]) {
_log('playFromUri($uri, $extras)');
return super.playFromUri(uri, extras);
}
@override
Future playMediaItem(MediaItem mediaItem) {
_log('playMediaItem($mediaItem)');
return super.playMediaItem(mediaItem);
}
@override
Future pause() {
_log('pause()');
return super.pause();
}
@override
Future click([MediaButton button = MediaButton.media]) {
_log('click($button)');
return super.click(button);
}
@override
Future stop() {
_log('stop()');
return super.stop();
}
@override
Future addQueueItem(MediaItem mediaItem) {
_log('addQueueItem($mediaItem)');
return super.addQueueItem(mediaItem);
}
@override
Future addQueueItems(List mediaItems) {
_log('addQueueItems($mediaItems)');
return super.addQueueItems(mediaItems);
}
@override
Future insertQueueItem(int index, MediaItem mediaItem) {
_log('insertQueueItem($index, $mediaItem)');
return super.insertQueueItem(index, mediaItem);
}
@override
Future updateQueue(List queue) {
_log('updateQueue($queue)');
return super.updateQueue(queue);
}
@override
Future updateMediaItem(MediaItem mediaItem) {
_log('updateMediaItem($mediaItem)');
return super.updateMediaItem(mediaItem);
}
@override
Future removeQueueItem(MediaItem mediaItem) {
_log('removeQueueItem($mediaItem)');
return super.removeQueueItem(mediaItem);
}
@override
Future removeQueueItemAt(int index) {
_log('removeQueueItemAt($index)');
return super.removeQueueItemAt(index);
}
@override
Future skipToNext() {
_log('skipToNext()');
return super.skipToNext();
}
@override
Future skipToPrevious() {
_log('skipToPrevious()');
return super.skipToPrevious();
}
@override
Future fastForward() {
_log('fastForward()');
return super.fastForward();
}
@override
Future rewind() {
_log('rewind()');
return super.rewind();
}
@override
Future skipToQueueItem(int index) {
_log('skipToQueueItem($index)');
return super.skipToQueueItem(index);
}
@override
Future seek(Duration position) {
_log('seek($position)');
return super.seek(position);
}
@override
Future setRating(Rating rating, Map? extras) {
_log('setRating($rating, $extras)');
return super.setRating(rating, extras);
}
@override
Future setCaptioningEnabled(bool enabled) {
_log('setCaptioningEnabled($enabled)');
return super.setCaptioningEnabled(enabled);
}
@override
Future setRepeatMode(AudioServiceRepeatMode repeatMode) {
_log('setRepeatMode($repeatMode)');
return super.setRepeatMode(repeatMode);
}
@override
Future setShuffleMode(AudioServiceShuffleMode shuffleMode) {
_log('setShuffleMode($shuffleMode)');
return super.setShuffleMode(shuffleMode);
}
@override
Future seekBackward(bool begin) {
_log('seekBackward($begin)');
return super.seekBackward(begin);
}
@override
Future seekForward(bool begin) {
_log('seekForward($begin)');
return super.seekForward(begin);
}
@override
Future setSpeed(double speed) {
_log('setSpeed($speed)');
return super.setSpeed(speed);
}
@override
Future customAction(
String name, Map? extras) async {
_log('customAction($name, extras)');
final result = await super.customAction(name, extras);
_log('customAction -> $result');
return result;
}
@override
Future onTaskRemoved() {
_log('onTaskRemoved()');
return super.onTaskRemoved();
}
@override
Future onNotificationDeleted() {
_log('onNotificationDeleted()');
return super.onNotificationDeleted();
}
@override
Future
> getChildren(String parentMediaId, [Map? options]) async {
_log('getChildren($parentMediaId, $options)');
final result = await super.getChildren(parentMediaId, options);
_log('getChildren -> $result');
return result;
}
@override
ValueStream
To Reproduce (i.e. user steps, not code) see above
Expected behavior
only title expected - "TITLE" and "Science Friday and WNYC Studios" only subtitle expected - "A Salute To Head-Scratching Science" and "SUBTITLE"
Screenshots If applicable, add screenshots to help explain your problem.
Runtime Environment (please complete the following information if relevant): android
Flutter SDK version
flutter doctor -v
``` [✓] Flutter (Channel master, 2.1.0-13.0.pre.574, on Microsoft Windows [Version 10.0.19041.867], locale ru-RU) • Flutter version 2.1.0-13.0.pre.574 at c:\dev\src\flutter • Framework revision 02efffc134 (33 hours ago), 2021-04-10 03:49:01 -0400 • Engine revision 8863afff16 • Dart version 2.13.0 (build 2.13.0-222.0.dev) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) • Android SDK at C:\Users\danya\AppData\Local\Android\sdk • Platform android-30, build-tools 30.0.2 • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01) • All Android licenses accepted. [✓] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [✓] Visual Studio - develop for Windows (Visual Studio Community 2019 16.7.7) • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community • Visual Studio Community 2019 version 16.7.30621.155 • Windows 10 SDK version 10.0.19041.0 [✓] Android Studio (version 4.0) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin version 50.0.1 • Dart plugin version 193.7547 • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01) [✓] IntelliJ IDEA Community Edition (version 2020.3) • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3 • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.55.1) • VS Code at C:\Users\danya\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.21.0 [✓] Connected device (4 available) • sdk gphone x86 (mobile) • emulator-5554 • android-x86 • Android 11 (API 30) (emulator) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19041.867] • Chrome (web) • chrome • web-javascript • Google Chrome 89.0.4389.114 • Edge (web) • edge • web-javascript • Microsoft Edge 89.0.774.63 • No issues found! ```Additional context btw i'm not sure what is the displayDescriptions and what it does, would like to know that as well