-
Notifications
You must be signed in to change notification settings - Fork 473
Android service update #2897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Android service update #2897
Changes from 4 commits
b631602
ea992b3
3dfb124
dc0cc91
c0c7eb8
efdde5b
336d5ab
33da943
a712311
d1f2fc3
83cac6c
d33a686
e4fcf8e
b95adbb
e779753
1acf0f0
d7dd2ef
5dff776
e33100a
fd4e473
7971fd3
7e286ac
3603e79
bc3d944
55b6062
edfed8e
ca87c60
7ca828b
8a27ddc
8d4139f
4a2dd06
9155b08
e777736
cbb31ad
cfc48ec
b7084c7
2a96736
3ac2469
5e2aac8
badbcc8
373086b
02c7e49
7fd4161
8c5677b
2131c78
93e22f8
29d1290
df44b04
0689336
aead30e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,8 +4,8 @@ | |||||
| xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
| android:id="@+id/texture_view_media_element" | ||||||
| app:surface_type="texture_view" | ||||||
| app:shutter_background_color="#FFFFFF" | ||||||
| app:shutter_background_color="#000000" | ||||||
|
||||||
| app:shutter_background_color="#000000" | |
| app:shutter_background_color="#FFFFFF" |
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,180 +1,117 @@ | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Runtime.Versioning; | ||
| using System.Runtime.Versioning; | ||
| using Android.App; | ||
| using Android.Content; | ||
| using Android.Content.PM; | ||
| using Android.OS; | ||
| using AndroidX.Core.App; | ||
| using AndroidX.Media3.Common; | ||
| using AndroidX.Media3.DataSource; | ||
| using AndroidX.Media3.ExoPlayer; | ||
| using AndroidX.Media3.ExoPlayer.TrackSelection; | ||
| using AndroidX.Media3.Session; | ||
| using AndroidX.Media3.UI; | ||
| using CommunityToolkit.Maui.Services; | ||
| using Java.Util; | ||
| using Resource = Microsoft.Maui.Controls.Resource; | ||
|
|
||
| namespace CommunityToolkit.Maui.Media.Services; | ||
|
|
||
| [SupportedOSPlatform("Android26.0")] | ||
| [IntentFilter(["androidx.media3.session.MediaSessionService"])] | ||
| [Service(Exported = false, Enabled = true, Name = "communityToolkit.maui.media.services", ForegroundServiceType = ForegroundService.TypeMediaPlayback)] | ||
| sealed partial class MediaControlsService : Service | ||
| sealed partial class MediaControlsService : MediaSessionService | ||
| { | ||
| readonly WeakEventManager taskRemovedEventManager = new(); | ||
| const string cHANNEL_ID = "media_playback_channel"; | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const int nOTIFICATION_ID = 1001; | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| bool isDisposed; | ||
| MediaSession? mediaSession; | ||
| public IExoPlayer? ExoPlayer; | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| PlayerNotificationManager? playerNotificationManager; | ||
| NotificationCompat.Builder? notificationBuilder; | ||
|
|
||
| public event EventHandler TaskRemoved | ||
| public override void OnTaskRemoved(Intent? rootIntent) | ||
| { | ||
| add => taskRemovedEventManager.AddEventHandler(value); | ||
| remove => taskRemovedEventManager.RemoveEventHandler(value); | ||
| base.OnTaskRemoved(rootIntent); | ||
| PauseAllPlayersAndStopSelf(); | ||
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public BoundServiceBinder? Binder { get; private set; } | ||
| public NotificationManager? NotificationManager { get; private set; } | ||
| public NotificationManager? NotificationManager { get; set; } | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public override IBinder? OnBind(Intent? intent) | ||
| { | ||
| Binder = new BoundServiceBinder(this); | ||
| return Binder; | ||
| } | ||
|
|
||
| public override void OnCreate() | ||
| { | ||
| base.OnCreate(); | ||
| StartForegroundServices(); | ||
| } | ||
|
|
||
| public override StartCommandResult OnStartCommand(Intent? intent, StartCommandFlags flags, int startId) | ||
| => StartCommandResult.NotSticky; | ||
|
|
||
| public override void OnTaskRemoved(Intent? rootIntent) | ||
| { | ||
| base.OnTaskRemoved(rootIntent); | ||
| taskRemovedEventManager.HandleEvent(this, EventArgs.Empty, nameof(TaskRemoved)); | ||
|
|
||
| playerNotificationManager?.SetPlayer(null); | ||
| NotificationManager?.CancelAll(); | ||
| CreateNotificationChannel(); | ||
|
|
||
| StartForeground(nOTIFICATION_ID, CreateNotification()); | ||
|
|
||
| var audioAttribute = new AndroidX.Media3.Common.AudioAttributes.Builder()? | ||
| .SetContentType(C.AudioContentTypeMusic)? // When phonecalls come in, music is paused | ||
| .SetUsage(C.UsageMedia)? | ||
| .Build(); | ||
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| var trackSelector = new DefaultTrackSelector(this); | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var trackSelectionParameters = trackSelector.BuildUponParameters()? | ||
| .SetPreferredAudioLanguage(C.LanguageUndetermined)? // Fallback to system language if no preferred language found | ||
| .SetPreferredTextLanguage(C.LanguageUndetermined)? // Fallback to system language if no preferred language found | ||
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .SetIgnoredTextSelectionFlags(C.SelectionReasonUnknown); // Ignore text tracks that are not explicitly selected by the user | ||
| trackSelector.SetParameters((DefaultTrackSelector.Parameters.Builder?)trackSelectionParameters); // Allows us to select tracks based on user preferences | ||
|
|
||
| var loadControlBuilder = new DefaultLoadControl.Builder(); | ||
| loadControlBuilder.SetBufferDurationsMs( | ||
| minBufferMs: 15000, | ||
| maxBufferMs: 50000, | ||
| bufferForPlaybackMs: 2500, | ||
| bufferForPlaybackAfterRebufferMs: 5000); // Custom buffering strategy | ||
|
Comment on lines
+46
to
+50
|
||
|
|
||
| var builder = new ExoPlayerBuilder(this) ?? throw new InvalidOperationException("ExoPlayerBuilder.Build() returned null"); | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| builder.SetTrackSelector(trackSelector); | ||
| builder.SetAudioAttributes(audioAttribute, true); | ||
| builder.SetHandleAudioBecomingNoisy(true); // Unplugging headphones will pause playback | ||
| builder.SetLoadControl(loadControlBuilder.Build()); | ||
| ExoPlayer = builder.Build() ?? throw new InvalidOperationException("ExoPlayerBuilder.Build() returned null"); | ||
|
|
||
| var mediaSessionBuilder = new MediaSession.Builder(this, ExoPlayer); | ||
| UUID sessionId = UUID.RandomUUID() ?? throw new InvalidOperationException("UUID.RandomUUID() returned null"); | ||
| mediaSessionBuilder.SetId(sessionId.ToString()); | ||
|
|
||
| var dataSourceBitmapFactory = new DataSourceBitmapLoader(this); | ||
| mediaSessionBuilder.SetBitmapLoader(dataSourceBitmapFactory); | ||
| mediaSession = mediaSessionBuilder.Build() ?? throw new InvalidOperationException("MediaSession.Builder.Build() returned null"); | ||
| } | ||
|
|
||
| public override void OnDestroy() | ||
| { | ||
| base.OnDestroy(); | ||
|
|
||
| playerNotificationManager?.SetPlayer(null); | ||
| NotificationManager?.CancelAll(); | ||
| if (!OperatingSystem.IsAndroidVersionAtLeast(33)) | ||
| { | ||
| StopForeground(true); | ||
| } | ||
|
|
||
| StopSelf(); | ||
| PauseAllPlayersAndStopSelf(); | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public override void OnRebind(Intent? intent) | ||
| { | ||
| base.OnRebind(intent); | ||
| StartForegroundServices(); | ||
| } | ||
|
|
||
| [MemberNotNull(nameof(NotificationManager), nameof(notificationBuilder))] | ||
| public void UpdateNotifications(in MediaSession session, in PlatformMediaElement mediaElement) | ||
| void CreateNotificationChannel() | ||
| { | ||
| ArgumentNullException.ThrowIfNull(notificationBuilder); | ||
| ArgumentNullException.ThrowIfNull(NotificationManager); | ||
|
|
||
| var style = new MediaStyleNotificationHelper.MediaStyle(session); | ||
| if (!OperatingSystem.IsAndroidVersionAtLeast(33)) | ||
| var channel = new Android.App.NotificationChannel(cHANNEL_ID, "Media Playback", NotificationImportance.Low) | ||
| { | ||
| SetLegacyNotifications(session, mediaElement); | ||
| } | ||
|
|
||
| notificationBuilder.SetStyle(style); | ||
| NotificationManagerCompat.From(Platform.AppContext)?.Notify(1, notificationBuilder.Build()); | ||
| Description = "Media playback controls", | ||
| Name = "Media Playback" | ||
| }; | ||
| channel.SetShowBadge(false); | ||
| channel.LockscreenVisibility = NotificationVisibility.Public; | ||
| NotificationManager manager = GetSystemService(NotificationService) as NotificationManager ?? throw new InvalidOperationException($"{nameof(NotificationManager)} cannot be null"); | ||
| manager.CreateNotificationChannel(channel); | ||
| } | ||
|
|
||
| [MemberNotNull(nameof(playerNotificationManager))] | ||
| public void SetLegacyNotifications(in MediaSession session, in PlatformMediaElement mediaElement) | ||
| static Notification CreateNotification() | ||
| { | ||
| ArgumentNullException.ThrowIfNull(session); | ||
| playerNotificationManager ??= new PlayerNotificationManager.Builder(Platform.AppContext, 1, "1").Build() | ||
| ?? throw new InvalidOperationException("PlayerNotificationManager cannot be null"); | ||
|
|
||
| playerNotificationManager.SetUseFastForwardAction(true); | ||
| playerNotificationManager.SetUseFastForwardActionInCompactView(true); | ||
| playerNotificationManager.SetUseRewindAction(true); | ||
| playerNotificationManager.SetUseRewindActionInCompactView(true); | ||
| playerNotificationManager.SetUseNextAction(true); | ||
| playerNotificationManager.SetUseNextActionInCompactView(true); | ||
| playerNotificationManager.SetUsePlayPauseActions(true); | ||
| playerNotificationManager.SetUsePreviousAction(true); | ||
| playerNotificationManager.SetColor(Resource.Color.abc_primary_text_material_dark); | ||
| playerNotificationManager.SetUsePreviousActionInCompactView(true); | ||
| playerNotificationManager.SetVisibility(NotificationCompat.VisibilityPublic); | ||
| playerNotificationManager.SetMediaSessionToken(session.SessionCompatToken); | ||
| playerNotificationManager.SetPlayer(mediaElement); | ||
| playerNotificationManager.SetColorized(true); | ||
| playerNotificationManager.SetShowPlayButtonIfPlaybackIsSuppressed(true); | ||
| playerNotificationManager.SetSmallIcon(Resource.Drawable.media3_notification_small_icon); | ||
| playerNotificationManager.SetPriority(NotificationCompat.PriorityDefault); | ||
| playerNotificationManager.SetUseChronometer(true); | ||
| return new NotificationCompat.Builder(Platform.AppContext ?? throw new InvalidOperationException("AppContext cannot be null"), cHANNEL_ID) | ||
| .SetContentTitle("Playing Media")? | ||
| .SetContentText("Artist")? | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .SetSmallIcon(Resource.Drawable.notification_bg_low)? | ||
| .SetVisibility((int)NotificationVisibility.Public)? | ||
| .SetOngoing(true)? | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .Build() ?? throw new InvalidOperationException("Notification cannot be null"); | ||
ne0rrmatrix marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| protected override void Dispose(bool disposing) | ||
| public override MediaSession? OnGetSession(MediaSession.ControllerInfo? p0) | ||
| { | ||
| if (!isDisposed) | ||
| { | ||
| if (disposing) | ||
| { | ||
| NotificationManager?.Dispose(); | ||
| NotificationManager = null; | ||
|
|
||
| playerNotificationManager?.Dispose(); | ||
| playerNotificationManager = null; | ||
|
|
||
| if (!OperatingSystem.IsAndroidVersionAtLeast(33)) | ||
| { | ||
| StopForeground(true); | ||
| } | ||
|
|
||
| StopSelf(); | ||
| } | ||
|
|
||
| isDisposed = true; | ||
| } | ||
|
|
||
| base.Dispose(disposing); | ||
| return mediaSession; | ||
| } | ||
|
|
||
| static void CreateNotificationChannel(in NotificationManager notificationMnaManager) | ||
| { | ||
| var channel = new NotificationChannel("1", "1", NotificationImportance.Low); | ||
| notificationMnaManager.CreateNotificationChannel(channel); | ||
| } | ||
|
|
||
| [MemberNotNull(nameof(notificationBuilder), nameof(NotificationManager))] | ||
| void StartForegroundServices() | ||
| { | ||
| NotificationManager ??= GetSystemService(NotificationService) as NotificationManager ?? throw new InvalidOperationException($"{nameof(NotificationManager)} cannot be null"); | ||
| notificationBuilder ??= new NotificationCompat.Builder(Platform.AppContext, "1"); | ||
|
|
||
| notificationBuilder.SetSmallIcon(Resource.Drawable.media3_notification_small_icon); | ||
| notificationBuilder.SetAutoCancel(false); | ||
| notificationBuilder.SetForegroundServiceBehavior(NotificationCompat.ForegroundServiceImmediate); | ||
| notificationBuilder.SetVisibility(NotificationCompat.VisibilityPublic); | ||
|
|
||
| CreateNotificationChannel(NotificationManager); | ||
|
|
||
| if (OperatingSystem.IsAndroidVersionAtLeast(29)) | ||
| { | ||
| if (notificationBuilder.Build() is Notification notification) | ||
| { | ||
| StartForeground(1, notification, ForegroundService.TypeMediaPlayback); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| StartForeground(1, notificationBuilder.Build()); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.