Skip to content

Commit 956a1ae

Browse files
Remove circular dependency between MediaManager and MediaControlsService
1 parent bf2ad60 commit 956a1ae

File tree

5 files changed

+808
-788
lines changed

5 files changed

+808
-788
lines changed

src/CommunityToolkit.Maui.MediaElement/Services/BoundServiceConnection.android.cs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,47 @@
33
using CommunityToolkit.Maui.Core.Views;
44

55
namespace CommunityToolkit.Maui.Services;
6+
67
sealed partial class BoundServiceConnection(MediaManager mediaManager) : Java.Lang.Object, IServiceConnection
78
{
9+
readonly WeakEventManager taskRemovedEventManager = new();
10+
11+
public event EventHandler MediaControlsServiceTaskRemoved
12+
{
13+
add => taskRemovedEventManager.AddEventHandler(value);
14+
remove => taskRemovedEventManager.RemoveEventHandler(value);
15+
}
16+
817
public MediaManager? Activity { get; } = mediaManager;
918

10-
public bool IsConnected => isConnected;
11-
bool isConnected = false;
19+
public bool IsConnected => Binder is not null;
1220

13-
public BoundServiceBinder? Binder = null;
21+
public BoundServiceBinder? Binder { get; private set; }
1422

1523
void IServiceConnection.OnServiceConnected(ComponentName? name, IBinder? service)
1624
{
1725
Binder = service as BoundServiceBinder;
18-
isConnected = Binder is not null;
26+
27+
if (Binder is not null)
28+
{
29+
Binder.Service.TaskRemoved += HandleTaskRemoved;
30+
}
31+
1932
// UpdateNotifications needs to be called as it may have been called before the service was connected
2033
Activity?.UpdateNotifications();
2134
}
2235

36+
void HandleTaskRemoved(object? sender, EventArgs e)
37+
{
38+
taskRemovedEventManager.HandleEvent(this, EventArgs.Empty, nameof(MediaControlsServiceTaskRemoved));
39+
}
40+
2341
void IServiceConnection.OnServiceDisconnected(ComponentName? name)
2442
{
25-
isConnected = false;
26-
Binder = null;
43+
if (Binder is not null)
44+
{
45+
Binder.Service.TaskRemoved -= HandleTaskRemoved;
46+
Binder = null;
47+
}
2748
}
2849
}

src/CommunityToolkit.Maui.MediaElement/Services/MediaControlsService.android.cs

Lines changed: 158 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -16,149 +16,162 @@ namespace CommunityToolkit.Maui.Media.Services;
1616
[Service(Exported = false, Enabled = true, Name = "communityToolkit.maui.media.services", ForegroundServiceType = ForegroundService.TypeMediaPlayback)]
1717
sealed partial class MediaControlsService : Service
1818
{
19-
bool isDisposed;
20-
21-
public MediaSession? Session;
22-
public PlatformMediaElement? Player;
23-
24-
public NotificationManager? NotificationManager;
25-
PlayerNotificationManager? playerNotificationManager;
26-
NotificationCompat.Builder? notification;
27-
28-
public PlayerView? PlayerView { get; set; }
29-
30-
public BoundServiceBinder? Binder = null;
31-
32-
public override IBinder? OnBind(Intent? intent)
33-
{
34-
Binder = new BoundServiceBinder(this);
35-
return Binder;
36-
}
37-
public override void OnCreate()
38-
{
39-
base.OnCreate();
40-
StartForegroundServices();
41-
}
42-
public override StartCommandResult OnStartCommand(Intent? intent, StartCommandFlags flags, int startId)
43-
{
44-
return StartCommandResult.NotSticky;
45-
}
46-
47-
public override void OnTaskRemoved(Intent? rootIntent)
48-
{
49-
base.OnTaskRemoved(rootIntent);
50-
Player?.Stop();
51-
playerNotificationManager?.SetPlayer(null);
52-
NotificationManager?.CancelAll();
53-
}
54-
55-
public override void OnDestroy()
56-
{
57-
base.OnDestroy();
58-
playerNotificationManager?.SetPlayer(null);
59-
NotificationManager?.CancelAll();
60-
if (OperatingSystem.IsAndroidVersionAtLeast(26) && !OperatingSystem.IsAndroidVersionAtLeast(33))
61-
{
62-
StopForeground(true);
63-
}
64-
StopSelf();
65-
}
66-
protected override void Dispose(bool disposing)
67-
{
68-
if (!isDisposed)
69-
{
70-
if (disposing)
71-
{
72-
NotificationManager?.Dispose();
73-
playerNotificationManager?.Dispose();
74-
if (OperatingSystem.IsAndroidVersionAtLeast(26) && !OperatingSystem.IsAndroidVersionAtLeast(33))
75-
{
76-
StopForeground(true);
77-
}
78-
StopSelf();
79-
}
80-
isDisposed = true;
81-
}
82-
base.Dispose(disposing);
83-
}
84-
85-
public override void OnRebind(Intent? intent)
86-
{
87-
base.OnRebind(intent);
88-
StartForegroundServices();
89-
}
90-
91-
[MemberNotNull(nameof(NotificationManager))]
92-
static void CreateNotificationChannel(NotificationManager notificationMnaManager)
93-
{
94-
if (OperatingSystem.IsAndroidVersionAtLeast(26))
95-
{
96-
var channel = new NotificationChannel("1", "1", NotificationImportance.Low);
97-
notificationMnaManager.CreateNotificationChannel(channel);
98-
}
99-
}
100-
101-
[MemberNotNull(nameof(notification), nameof(NotificationManager))]
102-
void StartForegroundServices()
103-
{
104-
NotificationManager ??= GetSystemService(NotificationService) as NotificationManager ?? throw new InvalidOperationException($"{nameof(NotificationManager)} cannot be null");
105-
notification ??= new NotificationCompat.Builder(Platform.AppContext, "1");
106-
107-
notification.SetSmallIcon(Resource.Drawable.media3_notification_small_icon);
108-
notification.SetAutoCancel(false);
109-
notification.SetForegroundServiceBehavior(NotificationCompat.ForegroundServiceImmediate);
110-
notification.SetVisibility(NotificationCompat.VisibilityPublic);
111-
112-
CreateNotificationChannel(NotificationManager);
113-
114-
if (OperatingSystem.IsAndroidVersionAtLeast(29))
115-
{
116-
StartForeground(1, notification.Build(), ForegroundService.TypeMediaPlayback);
117-
return;
118-
}
119-
120-
StartForeground(1, notification.Build());
121-
}
122-
123-
[MemberNotNull(nameof(NotificationManager), nameof(notification))]
124-
public void UpdateNotifications()
125-
{
126-
ArgumentNullException.ThrowIfNull(notification);
127-
ArgumentNullException.ThrowIfNull(NotificationManager);
128-
129-
var style = new MediaStyleNotificationHelper.MediaStyle(Session);
130-
if (!OperatingSystem.IsAndroidVersionAtLeast(33))
131-
{
132-
SetLegacyNotifications();
133-
}
134-
notification.SetStyle(style);
135-
NotificationManagerCompat.From(Platform.AppContext).Notify(1, notification.Build());
136-
}
137-
138-
[MemberNotNull(nameof(playerNotificationManager))]
139-
public void SetLegacyNotifications()
140-
{
141-
playerNotificationManager ??= new PlayerNotificationManager.Builder(Platform.AppContext, 1, "1").Build();
142-
ArgumentNullException.ThrowIfNull(Session);
143-
ArgumentNullException.ThrowIfNull(playerNotificationManager);
144-
145-
playerNotificationManager.SetUseFastForwardAction(true);
146-
playerNotificationManager.SetUseFastForwardActionInCompactView(true);
147-
playerNotificationManager.SetUseRewindAction(true);
148-
playerNotificationManager.SetUseRewindActionInCompactView(true);
149-
playerNotificationManager.SetUseNextAction(true);
150-
playerNotificationManager.SetUseNextActionInCompactView(true);
151-
playerNotificationManager.SetUsePlayPauseActions(true);
152-
playerNotificationManager.SetUsePreviousAction(true);
153-
playerNotificationManager.SetColor(Resource.Color.abc_primary_text_material_dark);
154-
playerNotificationManager.SetUsePreviousActionInCompactView(true);
155-
playerNotificationManager.SetVisibility(NotificationCompat.VisibilityPublic);
156-
playerNotificationManager.SetMediaSessionToken(Session.SessionCompatToken);
157-
playerNotificationManager.SetPlayer(Player);
158-
playerNotificationManager.SetColorized(true);
159-
playerNotificationManager.SetShowPlayButtonIfPlaybackIsSuppressed(true);
160-
playerNotificationManager.SetSmallIcon(Resource.Drawable.media3_notification_small_icon);
161-
playerNotificationManager.SetPriority(NotificationCompat.PriorityDefault);
162-
playerNotificationManager.SetUseChronometer(true);
163-
}
19+
readonly WeakEventManager taskRemovedEventManager = new();
20+
21+
bool isDisposed;
22+
23+
PlayerNotificationManager? playerNotificationManager;
24+
NotificationCompat.Builder? notification;
25+
26+
public event EventHandler TaskRemoved
27+
{
28+
add => taskRemovedEventManager.AddEventHandler(value);
29+
remove => taskRemovedEventManager.RemoveEventHandler(value);
30+
}
31+
32+
public BoundServiceBinder? Binder { get; private set; }
33+
public NotificationManager? NotificationManager { get; private set; }
34+
35+
public override IBinder? OnBind(Intent? intent)
36+
{
37+
Binder = new BoundServiceBinder(this);
38+
return Binder;
39+
}
40+
41+
public override void OnCreate()
42+
{
43+
base.OnCreate();
44+
StartForegroundServices();
45+
}
46+
47+
public override StartCommandResult OnStartCommand(Intent? intent, StartCommandFlags flags, int startId)
48+
=> StartCommandResult.NotSticky;
49+
50+
public override void OnTaskRemoved(Intent? rootIntent)
51+
{
52+
base.OnTaskRemoved(rootIntent);
53+
taskRemovedEventManager.HandleEvent(this, EventArgs.Empty, nameof(TaskRemoved));
54+
55+
playerNotificationManager?.SetPlayer(null);
56+
NotificationManager?.CancelAll();
57+
}
58+
59+
public override void OnDestroy()
60+
{
61+
base.OnDestroy();
62+
63+
playerNotificationManager?.SetPlayer(null);
64+
NotificationManager?.CancelAll();
65+
if (!OperatingSystem.IsAndroidVersionAtLeast(33))
66+
{
67+
StopForeground(true);
68+
}
69+
70+
StopSelf();
71+
}
72+
73+
protected override void Dispose(bool disposing)
74+
{
75+
if (!isDisposed)
76+
{
77+
if (disposing)
78+
{
79+
NotificationManager?.Dispose();
80+
NotificationManager = null;
81+
82+
playerNotificationManager?.Dispose();
83+
playerNotificationManager = null;
84+
85+
if (!OperatingSystem.IsAndroidVersionAtLeast(33))
86+
{
87+
StopForeground(true);
88+
}
89+
90+
StopSelf();
91+
}
92+
93+
isDisposed = true;
94+
}
95+
96+
base.Dispose(disposing);
97+
}
98+
99+
public override void OnRebind(Intent? intent)
100+
{
101+
base.OnRebind(intent);
102+
StartForegroundServices();
103+
}
104+
105+
[MemberNotNull(nameof(NotificationManager), nameof(notification))]
106+
public void UpdateNotifications(in MediaSession session, in PlatformMediaElement mediaElement)
107+
{
108+
ArgumentNullException.ThrowIfNull(notification);
109+
ArgumentNullException.ThrowIfNull(NotificationManager);
110+
111+
var style = new MediaStyleNotificationHelper.MediaStyle(session);
112+
if (!OperatingSystem.IsAndroidVersionAtLeast(33))
113+
{
114+
SetLegacyNotifications(session, mediaElement);
115+
}
116+
117+
notification.SetStyle(style);
118+
NotificationManagerCompat.From(Platform.AppContext).Notify(1, notification.Build());
119+
}
120+
121+
[MemberNotNull(nameof(playerNotificationManager))]
122+
public void SetLegacyNotifications(in MediaSession session, in PlatformMediaElement mediaElement)
123+
{
124+
ArgumentNullException.ThrowIfNull(session);
125+
playerNotificationManager ??= new PlayerNotificationManager.Builder(Platform.AppContext, 1, "1").Build()
126+
?? throw new InvalidOperationException("PlayerNotificationManager cannot be null");
127+
128+
playerNotificationManager.SetUseFastForwardAction(true);
129+
playerNotificationManager.SetUseFastForwardActionInCompactView(true);
130+
playerNotificationManager.SetUseRewindAction(true);
131+
playerNotificationManager.SetUseRewindActionInCompactView(true);
132+
playerNotificationManager.SetUseNextAction(true);
133+
playerNotificationManager.SetUseNextActionInCompactView(true);
134+
playerNotificationManager.SetUsePlayPauseActions(true);
135+
playerNotificationManager.SetUsePreviousAction(true);
136+
playerNotificationManager.SetColor(Resource.Color.abc_primary_text_material_dark);
137+
playerNotificationManager.SetUsePreviousActionInCompactView(true);
138+
playerNotificationManager.SetVisibility(NotificationCompat.VisibilityPublic);
139+
playerNotificationManager.SetMediaSessionToken(session.SessionCompatToken);
140+
playerNotificationManager.SetPlayer(mediaElement);
141+
playerNotificationManager.SetColorized(true);
142+
playerNotificationManager.SetShowPlayButtonIfPlaybackIsSuppressed(true);
143+
playerNotificationManager.SetSmallIcon(Resource.Drawable.media3_notification_small_icon);
144+
playerNotificationManager.SetPriority(NotificationCompat.PriorityDefault);
145+
playerNotificationManager.SetUseChronometer(true);
146+
}
147+
148+
149+
static void CreateNotificationChannel(in NotificationManager notificationMnaManager)
150+
{
151+
var channel = new NotificationChannel("1", "1", NotificationImportance.Low);
152+
notificationMnaManager.CreateNotificationChannel(channel);
153+
}
154+
155+
[MemberNotNull(nameof(notification), nameof(NotificationManager))]
156+
void StartForegroundServices()
157+
{
158+
NotificationManager ??= GetSystemService(NotificationService) as NotificationManager ?? throw new InvalidOperationException($"{nameof(NotificationManager)} cannot be null");
159+
notification ??= new NotificationCompat.Builder(Platform.AppContext, "1");
160+
161+
notification.SetSmallIcon(Resource.Drawable.media3_notification_small_icon);
162+
notification.SetAutoCancel(false);
163+
notification.SetForegroundServiceBehavior(NotificationCompat.ForegroundServiceImmediate);
164+
notification.SetVisibility(NotificationCompat.VisibilityPublic);
165+
166+
CreateNotificationChannel(NotificationManager);
167+
168+
if (OperatingSystem.IsAndroidVersionAtLeast(29))
169+
{
170+
StartForeground(1, notification.Build(), ForegroundService.TypeMediaPlayback);
171+
}
172+
else
173+
{
174+
StartForeground(1, notification.Build());
175+
}
176+
}
164177
}

0 commit comments

Comments
 (0)