XamarinMediaManager icon indicating copy to clipboard operation
XamarinMediaManager copied to clipboard

Lock screen subtitle is always "unknown" in Samsung S10+

Open justcoding121 opened this issue 3 years ago • 5 comments

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

justcoding121 avatar Apr 04 '21 03:04 justcoding121

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).

justcoding121 avatar Apr 04 '21 04:04 justcoding121

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();
	}

}

justcoding121 avatar Apr 05 '21 09:04 justcoding121

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!

Eryens avatar Apr 07 '21 08:04 Eryens

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";)

image

If i don't set DisplayTitle then i get the CrossMediaManager.Current.Queue.Current.Title on both rows. image image

Same happends on the lockscreen for the emulator. But on the phone only first row shows value second row shows unknown/Okänd. image

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

SebulbaSe avatar May 16 '21 16:05 SebulbaSe

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;
}

janwiebe-jump avatar Jul 23 '21 07:07 janwiebe-jump