XamarinMediaManager
XamarinMediaManager copied to clipboard
Lock screen subtitle is always "unknown" in Samsung S10+
Device
Samsung S10+ running Android 11
🐛 Bug Report
On lock screen the subtitle is always shown as "unknown". Not able to change that so far with my various attempts. Its not using any of the media item metadata, so not sure where its pulling that text from.
Expected behavior
The notification subtitle should have a meaningful text from metadata of the mp3 file
Reproduction steps
Please find sample repository here to reproduce this issue. The repository is just a basic single page Xamarin.Forms app created with visual studio 2019.
Relevant code added to the default blank app is in below two lines, result is shown in screenshot above.
https://github.com/justcoding121/xamarin-sandbox/blob/main/Xamarin.SandboxApp/Xamarin.SandboxApp/Xamarin.SandboxApp.Android/MainActivity.cs#L26
https://github.com/justcoding121/xamarin-sandbox/blob/main/Xamarin.SandboxApp/Xamarin.SandboxApp/Xamarin.SandboxApp/MainPage.xaml.cs#L19
Configuration
Version: 1.0.8
Platform:
- [ ] :iphone: iOS
- [X] :robot: Android
- [ ] :checkered_flag: WPF
- [ ] :earth_americas: UWP
- [ ] :apple: MacOS
- [ ] :tv: tvOS
- [ ] :monkey: Xamarin.Forms
There seem to be a similar issue in Exoplayer repository, not sure if that helps. Relevant comments below.
https://github.com/google/ExoPlayer/issues/5223#issuecomment-446275387
Samsung apparently is using different metatdata keys than other vendors, at least in case of the title.
The MediaSessionConnector does copy metadata entries available in the MediaDescriptionCompat of a selected queue item to MediaMetadataCompat.Builder to give the media session information about the item currently being played. There is no officially specified mapping for properties of the MediaDescriptionCompat and the MediaMetadataCompat. What the connector does is best effort only.
For the title the mapping is pretty obvious, and we provided a fix to copy what we get from mediaDescriptionCompat.getTitle() to both MediaMetadataCompat.METADATA_KEY_TITLE and MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE which should make this work for the title of the track. (https://github.com/google/ExoPlayer/blob/release-v2/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java#L739)
There is no attribute 'artist' which we can get from the MediaDescriptionCompat. So If you want to add additional metadata or change the current mapping you need to use a MediaSessionConnector.MediaMetadataProvider and map everything yourself. The is called when the active queue item changes and the MediaMetadataCompat object which is returned by the provider is set to the session.
https://github.com/google/ExoPlayer/issues/5223#issuecomment-447063474
The keys Samsung are using are MediaMetadataCompat.METADATA_KEY_TITLE (for the top line) and MediaMetadataCompat.METADATA_KEY_ARTIST (for the second line).
I did a workaround for this by implementing a MetaDataProvider. It seems like the default meta data provider was not able to find METADATA_KEY_ARTIST from the mp3 file attributes. So I used METADATA_KEY_DISPLAY_SUBTITLE as METADATA_KEY_ARTIST value.
Added below line in AndroidMediaPlayer.cs line 276
MediaSessionConnector.SetMediaMetadataProvider(new MetaDataProvider(MediaSession.Controller, null));
public class MetaDataProvider : Java.Lang.Object, IMediaMetadataProvider
{
private DefaultMediaMetadataProvider defaultMediaMetadataProvider;
public MetaDataProvider(
MediaControllerCompat mediaController, String metadataExtrasPrefix)
{
defaultMediaMetadataProvider =
new DefaultMediaMetadataProvider(mediaController, metadataExtrasPrefix);
}
public MediaMetadataCompat GetMetadata(IPlayer player)
{
var mediaMetadata = defaultMediaMetadataProvider.GetMetadata(player);
var builder = new MediaMetadataCompat.Builder();
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyTitle))
{
builder.PutString(MediaMetadataCompat.MetadataKeyTitle, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyTitle));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyArtist) && !string.IsNullOrWhiteSpace(mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyArtist)))
{
builder.PutString(MediaMetadataCompat.MetadataKeyArtist, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyArtist));
}
else
{
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDisplaySubtitle))
{
builder.PutString(MediaMetadataCompat.MetadataKeyArtist, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyDisplaySubtitle));
}
else
{
//hard-coded here
//TODO: Use another non-null metadata here
builder.PutString(MediaMetadataCompat.MetadataKeyArtist, "Bible Alarm");
}
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyMediaUri))
{
builder.PutString(MediaMetadataCompat.MetadataKeyMediaUri, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyMediaUri));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyAdvertisement))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyAdvertisement, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyAdvertisement));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyAlbum))
{
builder.PutString(MediaMetadataCompat.MetadataKeyAlbum, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyAlbum));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyAlbumArt))
{
builder.PutBitmap(MediaMetadataCompat.MetadataKeyAlbumArt, mediaMetadata.GetBitmap(MediaMetadataCompat.MetadataKeyAlbumArt));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyAlbumArtist))
{
builder.PutString(MediaMetadataCompat.MetadataKeyAlbumArtist, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyAlbumArtist));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyAlbumArtUri))
{
builder.PutString(MediaMetadataCompat.MetadataKeyAlbumArtUri, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyAlbumArtUri));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyArt))
{
builder.PutBitmap(MediaMetadataCompat.MetadataKeyArt, mediaMetadata.GetBitmap(MediaMetadataCompat.MetadataKeyArt));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyArtUri))
{
builder.PutString(MediaMetadataCompat.MetadataKeyArtUri, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyArtUri));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyAuthor))
{
builder.PutString(MediaMetadataCompat.MetadataKeyAuthor, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyAuthor));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyCompilation))
{
builder.PutString(MediaMetadataCompat.MetadataKeyCompilation, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyCompilation));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyComposer))
{
builder.PutString(MediaMetadataCompat.MetadataKeyComposer, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyComposer));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyRating))
{
builder.PutString(MediaMetadataCompat.MetadataKeyRating, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyRating));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDiscNumber))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyDiscNumber, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyDiscNumber));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDisplayDescription))
{
builder.PutString(MediaMetadataCompat.MetadataKeyDisplayDescription, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyDisplayDescription));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDisplayIcon))
{
builder.PutBitmap(MediaMetadataCompat.MetadataKeyDisplayIcon, mediaMetadata.GetBitmap(MediaMetadataCompat.MetadataKeyDisplayIcon));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDisplayIconUri))
{
builder.PutString(MediaMetadataCompat.MetadataKeyDisplayIconUri, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyDisplayIconUri));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDisplaySubtitle))
{
builder.PutString(MediaMetadataCompat.MetadataKeyDisplaySubtitle, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyDisplaySubtitle));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDisplayTitle))
{
builder.PutString(MediaMetadataCompat.MetadataKeyDisplayTitle, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyDisplayTitle));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDownloadStatus))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyDownloadStatus, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyDownloadStatus));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyDuration))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyDuration, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyDuration));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyGenre))
{
builder.PutString(MediaMetadataCompat.MetadataKeyGenre, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyGenre));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyMediaId))
{
builder.PutString(MediaMetadataCompat.MetadataKeyMediaId, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyMediaId));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyMediaUri))
{
builder.PutString(MediaMetadataCompat.MetadataKeyMediaUri, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyMediaUri));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyNumTracks))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyNumTracks, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyNumTracks));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyRating))
{
builder.PutRating(MediaMetadataCompat.MetadataKeyRating, mediaMetadata.GetRating(MediaMetadataCompat.MetadataKeyRating));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyTitle))
{
builder.PutString(MediaMetadataCompat.MetadataKeyTitle, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyTitle));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyTrackNumber))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyTrackNumber, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyTrackNumber));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyUserRating))
{
builder.PutRating(MediaMetadataCompat.MetadataKeyUserRating, mediaMetadata.GetRating(MediaMetadataCompat.MetadataKeyUserRating));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyWriter))
{
builder.PutString(MediaMetadataCompat.MetadataKeyWriter, mediaMetadata.GetString(MediaMetadataCompat.MetadataKeyWriter));
}
if (mediaMetadata.ContainsKey(MediaMetadataCompat.MetadataKeyYear))
{
builder.PutLong(MediaMetadataCompat.MetadataKeyYear, mediaMetadata.GetLong(MediaMetadataCompat.MetadataKeyYear));
}
return builder.Build();
}
}
Hello, I spent so many hours hours working on that a month ago and finally found a solution that doesn't require to mess around with the source code.
In Xamarin forms you have to create a dependency service for android and call it that way :
async Task<IMediaItem> CreateMediaItem(Uri videoUri)
{
IMediaItem mediaItem = await CrossMediaManager.Current.Extractor.CreateMediaItem(videoUri.AbsoluteUri);
mediaItem = DependencyService.Get<IWatchService>().AddMetaData(mediaItem, "Video Artist");
return mediaItem;
}
You can then play that media item with CrossMediaManager.Current.Play(mediaItem);
In this chunk of code I created a dependency service called WatchService which contains a function "AddMetaData"
Here is the code of this function
public IMediaItem AddMetaData(IMediaItem mediaItem, string mediaArtist)
{
Bundle bundle = new Bundle();
// For samsung phones
bundle.PutString(MediaMetadataCompat.MetadataKeyArtist, mediaArtist);
mediaItem.Extras = bundle;
return mediaItem;
}
The IMediaItem Extra property is of type object
, but is processed in the source code as a Bundle
. My first attempts were to pass a dictionary but it didn't work and I had to understand the source code to know this.
Maybe on the top of fixing that bug changing the type of the Extra property from object
to Bundle
would be a good change.
Hope this helps people!
Hi Having a similar problem. If i set the CrossMediaManager.Current.Queue.Current.DisplayTitle then it shows that value on both rows on the notificaiton on both phone and emulator. (CrossMediaManager.Current.Queue.Current.DisplayTitle = "DisplayTitle";)
If i don't set DisplayTitle then i get the CrossMediaManager.Current.Queue.Current.Title on both rows.
Same happends on the lockscreen for the emulator. But on the phone only first row shows value second row shows unknown/Okänd.
I have set the CrossMediaManager.Current.Queue.Current.DisplaySubTitle = "DisplaySubTitle" but i cant see it anywhere.
My phone is a Samsung S20 Note and the emulator is setup as a Pixel 2 x86 with OS : R 11.0 - API 30
Regards Magnus
I have seen this behaviour on Android11 and higher. I am using this code in such situations on Android:
// On Android >= 11, the mapping of the fields is different
// item is an IMediaItem instance
if(DeviceInfo.Version.Major >= 11)
{
item.DisplayTitle = track.Name;
item.DisplaySubtitle = track.Artist;
item.DisplayDescription = track.Artist;
// Add extra key to bundle to map the artist field to the subtitle of the notification.
Bundle bundle = new Bundle();
bundle.PutString(MediaMetadataCompat.MetadataKeyArtist, track.Artist);
item.Extras = bundle;
}