Merge pull request #77 from microsoft/feature/use-native-player-on-blazor-app
Feature/use native player on blazor apppull/89/head
commit
8153149ff8
|
@ -0,0 +1,24 @@
|
|||
namespace SharedMauiLib;
|
||||
|
||||
public interface INativeAudioService
|
||||
{
|
||||
Task InitializeAsync(string audioURI);
|
||||
|
||||
Task PlayAsync(double position = 0);
|
||||
|
||||
Task PauseAsync();
|
||||
|
||||
Task SetMuted(bool value);
|
||||
|
||||
Task SetVolume(int value);
|
||||
|
||||
Task SetCurrentTime(double value);
|
||||
|
||||
ValueTask DisposeAsync();
|
||||
|
||||
bool IsPlaying { get; }
|
||||
|
||||
double CurrentPosition { get; }
|
||||
|
||||
event EventHandler<bool> IsPlayingChanged;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace SharedMauiLib.Platforms.Android.CurrentActivity
|
||||
{
|
||||
public enum ActivityEvent
|
||||
{
|
||||
Created,
|
||||
Resumed,
|
||||
Paused,
|
||||
Destroyed,
|
||||
SaveInstanceState,
|
||||
Started,
|
||||
Stopped
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using Android.App;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android.CurrentActivity
|
||||
{
|
||||
public class ActivityEventArgs : EventArgs
|
||||
{
|
||||
internal ActivityEventArgs(Activity activity, ActivityEvent ev)
|
||||
{
|
||||
Event = ev;
|
||||
Activity = activity;
|
||||
}
|
||||
|
||||
public ActivityEvent Event { get; }
|
||||
public Activity Activity { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android.CurrentActivity
|
||||
{
|
||||
public class CrossCurrentActivity
|
||||
{
|
||||
static Lazy<ICurrentActivity> implementation = new Lazy<ICurrentActivity>(() => CreateCurrentActivity(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Current settings to use
|
||||
/// </summary>
|
||||
public static ICurrentActivity Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var ret = implementation.Value;
|
||||
if (ret == null)
|
||||
{
|
||||
throw NotImplementedInReferenceAssembly();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static ICurrentActivity CreateCurrentActivity()
|
||||
{
|
||||
#if NETSTANDARD1_0 || NETSTANDARD2_0
|
||||
return null;
|
||||
#else
|
||||
return new CurrentActivityImplementation();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static Exception NotImplementedInReferenceAssembly()
|
||||
{
|
||||
return new NotImplementedException("This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AndroidApp = Android.App;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android.CurrentActivity
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation for Feature
|
||||
/// </summary>
|
||||
[Preserve(AllMembers = true)]
|
||||
public class CurrentActivityImplementation : ICurrentActivity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the activity.
|
||||
/// </summary>
|
||||
/// <value>The activity.</value>
|
||||
public Activity Activity
|
||||
{
|
||||
get => lifecycleListener?.Activity;
|
||||
set
|
||||
{
|
||||
if (lifecycleListener == null)
|
||||
Init(value, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activity state changed event handler
|
||||
/// </summary>
|
||||
public event EventHandler<ActivityEventArgs> ActivityStateChanged;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Waits for an activity to be ready
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<Activity> WaitForActivityAsync(CancellationToken cancelToken = default)
|
||||
{
|
||||
if (Activity != null)
|
||||
return Activity;
|
||||
|
||||
var tcs = new TaskCompletionSource<Activity>();
|
||||
var handler = new EventHandler<ActivityEventArgs>((sender, args) =>
|
||||
{
|
||||
if (args.Event == ActivityEvent.Created || args.Event == ActivityEvent.Resumed)
|
||||
tcs.TrySetResult(args.Activity);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
using (cancelToken.Register(() => tcs.TrySetCanceled()))
|
||||
{
|
||||
ActivityStateChanged += handler;
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActivityStateChanged -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void RaiseStateChanged(Activity activity, ActivityEvent ev)
|
||||
=> ActivityStateChanged?.Invoke(this, new ActivityEventArgs(activity, ev));
|
||||
|
||||
|
||||
ActivityLifecycleContextListener lifecycleListener;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application context
|
||||
/// </summary>
|
||||
public Context AppContext =>
|
||||
AndroidApp.Application.Context;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize current activity with application
|
||||
/// </summary>
|
||||
/// <param name="application">The main application</param>
|
||||
public void Init(AndroidApp.Application application)
|
||||
{
|
||||
if (lifecycleListener != null)
|
||||
return;
|
||||
|
||||
lifecycleListener = new ActivityLifecycleContextListener();
|
||||
application.RegisterActivityLifecycleCallbacks(lifecycleListener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize current activity with activity!
|
||||
/// </summary>
|
||||
/// <param name="activity">The main activity</param>
|
||||
/// <param name="bundle">Bundle for activity </param>
|
||||
public void Init(Activity activity, Bundle bundle)
|
||||
{
|
||||
Init(activity.Application);
|
||||
lifecycleListener.Activity = activity;
|
||||
}
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
class ActivityLifecycleContextListener : Java.Lang.Object, AndroidApp.Application.IActivityLifecycleCallbacks
|
||||
{
|
||||
WeakReference<Activity> currentActivity = new WeakReference<Activity>(null);
|
||||
|
||||
public Context Context =>
|
||||
Activity ?? AndroidApp.Application.Context;
|
||||
|
||||
public Activity Activity
|
||||
{
|
||||
get => currentActivity.TryGetTarget(out var a) ? a : null;
|
||||
set => currentActivity.SetTarget(value);
|
||||
}
|
||||
|
||||
CurrentActivityImplementation Current =>
|
||||
(CurrentActivityImplementation)(CrossCurrentActivity.Current);
|
||||
|
||||
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
|
||||
{
|
||||
Activity = activity;
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.Created);
|
||||
}
|
||||
|
||||
public void OnActivityDestroyed(Activity activity)
|
||||
{
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.Destroyed);
|
||||
}
|
||||
|
||||
public void OnActivityPaused(Activity activity)
|
||||
{
|
||||
Activity = activity;
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.Paused);
|
||||
}
|
||||
|
||||
public void OnActivityResumed(Activity activity)
|
||||
{
|
||||
Activity = activity;
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.Resumed);
|
||||
}
|
||||
|
||||
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
|
||||
{
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.SaveInstanceState);
|
||||
}
|
||||
|
||||
public void OnActivityStarted(Activity activity)
|
||||
{
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.Started);
|
||||
}
|
||||
|
||||
public void OnActivityStopped(Activity activity)
|
||||
{
|
||||
Current.RaiseStateChanged(activity, ActivityEvent.Stopped);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using Android.Content;
|
||||
using Android.OS;
|
||||
using AndroidApp = Android.App;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android.CurrentActivity
|
||||
{
|
||||
/// <summary>
|
||||
/// Current Activity Interface
|
||||
/// </summary>
|
||||
public interface ICurrentActivity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the activity.
|
||||
/// </summary>
|
||||
/// <value>The activity.</value>
|
||||
AndroidApp.Activity Activity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Application Context.
|
||||
/// </summary>
|
||||
/// <value>The app context.</value>
|
||||
Context AppContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when activity state events are fired
|
||||
/// </summary>
|
||||
event EventHandler<ActivityEventArgs> ActivityStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Waits for an activity to be ready for use
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<AndroidApp.Activity> WaitForActivityAsync(CancellationToken cancelToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Current Activity Plugin with Application
|
||||
/// </summary>
|
||||
/// <param name="application"></param>
|
||||
void Init(AndroidApp.Application application);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the current activity with activity and bundle
|
||||
/// </summary>
|
||||
/// <param name="activity"></param>
|
||||
/// <param name="bundle"></param>
|
||||
void Init(AndroidApp.Activity activity, Bundle bundle);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace Microsoft.NetConf2021.Maui.Platforms.Android.Services;
|
||||
namespace SharedMauiLib.Platforms.Android;
|
||||
|
||||
public delegate void StatusChangedEventHandler(object sender, EventArgs e);
|
||||
|
||||
|
@ -7,3 +7,5 @@ public delegate void BufferingEventHandler(object sender, EventArgs e);
|
|||
public delegate void CoverReloadedEventHandler(object sender, EventArgs e);
|
||||
|
||||
public delegate void PlayingEventHandler(object sender, EventArgs e);
|
||||
|
||||
public delegate void PlayingChangedEventHandler(object sender, bool e);
|
|
@ -0,0 +1,15 @@
|
|||
namespace SharedMauiLib.Platforms.Android
|
||||
{
|
||||
public interface IAudioActivity
|
||||
{
|
||||
public MediaPlayerServiceBinder Binder { get; set; }
|
||||
|
||||
public event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public event CoverReloadedEventHandler CoverReloaded;
|
||||
|
||||
public event PlayingEventHandler Playing;
|
||||
|
||||
public event BufferingEventHandler Buffering;
|
||||
}
|
||||
}
|
|
@ -5,16 +5,15 @@ using Android.Net;
|
|||
using Android.Net.Wifi;
|
||||
using Android.OS;
|
||||
using Android.Media.Session;
|
||||
using Microsoft.NetConf2021.Maui.Platforms.Android.Receivers;
|
||||
using AndroidNet = Android.Net;
|
||||
using Android.Graphics;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.Android.Services;
|
||||
namespace SharedMauiLib.Platforms.Android;
|
||||
|
||||
|
||||
[Service(Exported = true)]
|
||||
[IntentFilter(new[] { ActionPlay, ActionPause, ActionStop, ActionTogglePlayback, ActionNext, ActionPrevious })]
|
||||
public class MediaPlayerService : Service,
|
||||
public class MediaPlayerService : Service,
|
||||
AudioManager.IOnAudioFocusChangeListener,
|
||||
MediaPlayer.IOnBufferingUpdateListener,
|
||||
MediaPlayer.IOnCompletionListener,
|
||||
|
@ -46,6 +45,12 @@ public class MediaPlayerService : Service,
|
|||
|
||||
public event BufferingEventHandler Buffering;
|
||||
|
||||
public event PlayingChangedEventHandler PlayingChanged;
|
||||
|
||||
public string AudioUrl;
|
||||
|
||||
public bool isCurrentEpisode = true;
|
||||
|
||||
private readonly Handler PlayingHandler;
|
||||
private readonly Java.Lang.Runnable PlayingHandlerRunnable;
|
||||
|
||||
|
@ -55,9 +60,9 @@ public class MediaPlayerService : Service,
|
|||
{
|
||||
get
|
||||
{
|
||||
return (mediaController.PlaybackState != null
|
||||
? mediaController.PlaybackState.State
|
||||
: PlaybackStateCode.None);
|
||||
return mediaController.PlaybackState != null
|
||||
? mediaController.PlaybackState.State
|
||||
: PlaybackStateCode.None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +71,8 @@ public class MediaPlayerService : Service,
|
|||
PlayingHandler = new Handler(Looper.MainLooper);
|
||||
|
||||
// Create a runnable, restarting itself if the status still is "playing"
|
||||
PlayingHandlerRunnable = new Java.Lang.Runnable(() => {
|
||||
PlayingHandlerRunnable = new Java.Lang.Runnable(() =>
|
||||
{
|
||||
OnPlaying(EventArgs.Empty);
|
||||
|
||||
if (MediaPlayerState == PlaybackStateCode.Playing)
|
||||
|
@ -76,7 +82,8 @@ public class MediaPlayerService : Service,
|
|||
});
|
||||
|
||||
// On Status changed to PLAYING, start raising the Playing event
|
||||
StatusChanged += (object sender, EventArgs e) => {
|
||||
StatusChanged += (sender, e) =>
|
||||
{
|
||||
if (MediaPlayerState == PlaybackStateCode.Playing)
|
||||
{
|
||||
PlayingHandler.PostDelayed(PlayingHandlerRunnable, 0);
|
||||
|
@ -89,6 +96,11 @@ public class MediaPlayerService : Service,
|
|||
StatusChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnPlayingChanged(bool e)
|
||||
{
|
||||
PlayingChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnCoverReloaded(EventArgs e)
|
||||
{
|
||||
if (CoverReloaded != null)
|
||||
|
@ -131,7 +143,7 @@ public class MediaPlayerService : Service,
|
|||
{
|
||||
if (mediaSession == null)
|
||||
{
|
||||
Intent nIntent = new Intent(ApplicationContext, typeof(MainActivity));
|
||||
Intent nIntent = new Intent(ApplicationContext, typeof(Activity));
|
||||
|
||||
remoteComponentName = new ComponentName(PackageName, new RemoteControlBroadcastReceiver().ComponentName);
|
||||
|
||||
|
@ -203,15 +215,13 @@ public class MediaPlayerService : Service,
|
|||
UpdatePlaybackState(PlaybackStateCode.Playing);
|
||||
}
|
||||
|
||||
public string AudioUrl ;
|
||||
|
||||
public int Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mediaPlayer == null
|
||||
|| (MediaPlayerState != PlaybackStateCode.Playing
|
||||
&& MediaPlayerState != PlaybackStateCode.Paused))
|
||||
|| MediaPlayerState != PlaybackStateCode.Playing
|
||||
&& MediaPlayerState != PlaybackStateCode.Paused)
|
||||
return -1;
|
||||
else
|
||||
return mediaPlayer.CurrentPosition;
|
||||
|
@ -223,8 +233,8 @@ public class MediaPlayerService : Service,
|
|||
get
|
||||
{
|
||||
if (mediaPlayer == null
|
||||
|| (MediaPlayerState != PlaybackStateCode.Playing
|
||||
&& MediaPlayerState != PlaybackStateCode.Paused))
|
||||
|| MediaPlayerState != PlaybackStateCode.Playing
|
||||
&& MediaPlayerState != PlaybackStateCode.Paused)
|
||||
return 0;
|
||||
else
|
||||
return mediaPlayer.Duration;
|
||||
|
@ -256,7 +266,7 @@ public class MediaPlayerService : Service,
|
|||
get
|
||||
{
|
||||
if (cover == null)
|
||||
cover = BitmapFactory.DecodeResource(Resources, Resource.Drawable.player_play);
|
||||
cover = BitmapFactory.DecodeResource(Resources, Resource.Drawable.abc_ab_share_pack_mtrl_alpha); //TODO player_play
|
||||
return cover;
|
||||
}
|
||||
private set
|
||||
|
@ -289,12 +299,14 @@ public class MediaPlayerService : Service,
|
|||
if (mediaSession == null)
|
||||
InitMediaSession();
|
||||
|
||||
if (mediaPlayer.IsPlaying)
|
||||
if (mediaPlayer.IsPlaying && isCurrentEpisode)
|
||||
{
|
||||
UpdatePlaybackState(PlaybackStateCode.Playing);
|
||||
return;
|
||||
}
|
||||
|
||||
isCurrentEpisode = true;
|
||||
|
||||
await PrepareAndPlayMediaPlayerAsync();
|
||||
}
|
||||
|
||||
|
@ -330,18 +342,14 @@ public class MediaPlayerService : Service,
|
|||
|
||||
byte[] imageByteArray = metaRetriever.GetEmbeddedPicture();
|
||||
if (imageByteArray == null)
|
||||
Cover = await BitmapFactory.DecodeResourceAsync(Resources, Resource.Drawable.player_play);
|
||||
Cover = await BitmapFactory.DecodeResourceAsync(Resources, Resource.Drawable.abc_ab_share_pack_mtrl_alpha); //TODO player_play
|
||||
else
|
||||
Cover = await BitmapFactory.DecodeByteArrayAsync(imageByteArray, 0, imageByteArray.Length);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdatePlaybackState(PlaybackStateCode.Stopped);
|
||||
|
||||
mediaPlayer.Reset();
|
||||
mediaPlayer.Release();
|
||||
mediaPlayer = null;
|
||||
UpdatePlaybackStateStopped();
|
||||
|
||||
// Unable to start playback log error
|
||||
Console.WriteLine(ex);
|
||||
|
@ -350,7 +358,8 @@ public class MediaPlayerService : Service,
|
|||
|
||||
public async Task Seek(int position)
|
||||
{
|
||||
await Task.Run(() => {
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.SeekTo(position);
|
||||
|
@ -396,7 +405,7 @@ public class MediaPlayerService : Service,
|
|||
|
||||
public async Task PlayPause()
|
||||
{
|
||||
if (mediaPlayer == null || (mediaPlayer != null && MediaPlayerState == PlaybackStateCode.Paused))
|
||||
if (mediaPlayer == null || mediaPlayer != null && MediaPlayerState == PlaybackStateCode.Paused)
|
||||
{
|
||||
await Play();
|
||||
}
|
||||
|
@ -408,7 +417,8 @@ public class MediaPlayerService : Service,
|
|||
|
||||
public async Task Pause()
|
||||
{
|
||||
await Task.Run(() => {
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (mediaPlayer == null)
|
||||
return;
|
||||
|
||||
|
@ -421,7 +431,8 @@ public class MediaPlayerService : Service,
|
|||
|
||||
public async Task Stop()
|
||||
{
|
||||
await Task.Run(() => {
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (mediaPlayer == null)
|
||||
return;
|
||||
|
||||
|
@ -439,6 +450,18 @@ public class MediaPlayerService : Service,
|
|||
});
|
||||
}
|
||||
|
||||
public void UpdatePlaybackStateStopped()
|
||||
{
|
||||
UpdatePlaybackState(PlaybackStateCode.Stopped);
|
||||
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.Reset();
|
||||
mediaPlayer.Release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlaybackState(PlaybackStateCode state)
|
||||
{
|
||||
if (mediaSession == null || mediaPlayer == null)
|
||||
|
@ -485,6 +508,16 @@ public class MediaPlayerService : Service,
|
|||
MediaPlayerState == PlaybackStateCode.Playing);
|
||||
}
|
||||
|
||||
internal void SetMuted(bool value)
|
||||
{
|
||||
mediaPlayer.SetVolume(0, 0);
|
||||
}
|
||||
|
||||
internal void SetVolume(int value)
|
||||
{
|
||||
mediaPlayer.SetVolume(value, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the metadata on the lock screen
|
||||
/// </summary>
|
||||
|
@ -662,13 +695,13 @@ public class MediaPlayerService : Service,
|
|||
|
||||
public override async void OnPause()
|
||||
{
|
||||
await mediaPlayerService.GetMediaPlayerService().Pause();
|
||||
mediaPlayerService.GetMediaPlayerService().OnPlayingChanged(false);
|
||||
base.OnPause();
|
||||
}
|
||||
|
||||
public override async void OnPlay()
|
||||
{
|
||||
await mediaPlayerService.GetMediaPlayerService().Play();
|
||||
mediaPlayerService.GetMediaPlayerService().OnPlayingChanged(true);
|
||||
base.OnPlay();
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Android.Content;
|
||||
using Android.OS;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android
|
||||
{
|
||||
public class MediaPlayerServiceConnection : Java.Lang.Object, IServiceConnection
|
||||
{
|
||||
readonly IAudioActivity instance;
|
||||
|
||||
public MediaPlayerServiceConnection(IAudioActivity mediaPlayer)
|
||||
{
|
||||
this.instance = mediaPlayer;
|
||||
}
|
||||
|
||||
public void OnServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
if (service is MediaPlayerServiceBinder binder)
|
||||
{
|
||||
instance.Binder = binder;
|
||||
|
||||
var mediaPlayerService = binder.GetMediaPlayerService();
|
||||
//mediaPlayerService.CoverReloaded += (object sender, EventArgs e) => { instance.CoverReloaded?.Invoke(sender, e); };
|
||||
//mediaPlayerService.StatusChanged += (object sender, EventArgs e) => { instance.StatusChanged?.Invoke(sender, e); };
|
||||
//mediaPlayerService.Playing += (object sender, EventArgs e) => { instance.Playing?.Invoke(sender, e); };
|
||||
//mediaPlayerService.Buffering += (object sender, EventArgs e) => { instance.Buffering?.Invoke(sender, e); };
|
||||
}
|
||||
}
|
||||
|
||||
public void OnServiceDisconnected(ComponentName name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
using Android.Media;
|
||||
using AndroidApp = Android.App;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android
|
||||
{
|
||||
public class NativeAudioService : INativeAudioService
|
||||
{
|
||||
IAudioActivity instance;
|
||||
|
||||
private MediaPlayer mediaPlayer => instance != null &&
|
||||
instance.Binder.GetMediaPlayerService() != null ?
|
||||
instance.Binder.GetMediaPlayerService().mediaPlayer : null;
|
||||
|
||||
public bool IsPlaying => mediaPlayer?.IsPlaying ?? false;
|
||||
|
||||
public double CurrentPosition => mediaPlayer?.CurrentPosition / 1000 ?? 0;
|
||||
public event EventHandler<bool> IsPlayingChanged;
|
||||
|
||||
public Task InitializeAsync(string audioURI)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
var activity = CurrentActivity.CrossCurrentActivity.Current;
|
||||
instance = activity.Activity as IAudioActivity;
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.Binder.GetMediaPlayerService().isCurrentEpisode = false;
|
||||
instance.Binder.GetMediaPlayerService().UpdatePlaybackStateStopped();
|
||||
}
|
||||
|
||||
this.instance.Binder.GetMediaPlayerService().PlayingChanged += (object sender, bool e) =>
|
||||
{
|
||||
Task.Run(async () => {
|
||||
if (e)
|
||||
{
|
||||
await this.PlayAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.PauseAsync();
|
||||
}
|
||||
});
|
||||
IsPlayingChanged?.Invoke(this, e);
|
||||
};
|
||||
|
||||
instance.Binder.GetMediaPlayerService().AudioUrl = audioURI;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
if (IsPlaying)
|
||||
{
|
||||
return instance.Binder.GetMediaPlayerService().Pause();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlayAsync(double position = 0)
|
||||
{
|
||||
await instance.Binder.GetMediaPlayerService().Play();
|
||||
await instance.Binder.GetMediaPlayerService().Seek((int)position * 1000);
|
||||
}
|
||||
|
||||
public Task SetMuted(bool value)
|
||||
{
|
||||
instance?.Binder.GetMediaPlayerService().SetMuted(value);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetVolume(int value)
|
||||
{
|
||||
instance?.Binder.GetMediaPlayerService().SetVolume(value);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetCurrentTime(double position)
|
||||
{
|
||||
return instance.Binder.GetMediaPlayerService().Seek((int)position * 1000);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
instance.Binder?.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,8 @@ using static Android.App.Notification;
|
|||
using static Android.Resource;
|
||||
using AndroidMedia = Android.Media;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.Android.Services;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android;
|
||||
|
||||
public static class NotificationHelper
|
||||
{
|
||||
|
@ -37,7 +38,7 @@ public static class NotificationHelper
|
|||
nm.CancelAll();
|
||||
}
|
||||
|
||||
internal static void CreateNotificationChannel(Context context)
|
||||
public static void CreateNotificationChannel(Context context)
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
{
|
||||
|
@ -59,50 +60,50 @@ public static class NotificationHelper
|
|||
}
|
||||
|
||||
internal static void StartNotification(
|
||||
Context context,
|
||||
Context context,
|
||||
MediaMetadata mediaMetadata,
|
||||
AndroidMedia.Session.MediaSession mediaSession,
|
||||
Object largeIcon,
|
||||
object largeIcon,
|
||||
bool isPlaying)
|
||||
{
|
||||
var pendingIntent = PendingIntent.GetActivity(
|
||||
context,
|
||||
0,
|
||||
new Intent(context, typeof(MainActivity)),
|
||||
new Intent(context, typeof(Activity)),
|
||||
PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Mutable);
|
||||
MediaMetadata currentTrack = mediaMetadata;
|
||||
|
||||
MediaStyle style = new MediaStyle();
|
||||
style.SetMediaSession(mediaSession.SessionToken);
|
||||
|
||||
var builder = new Notification.Builder(context, CHANNEL_ID)
|
||||
var builder = new Builder(context, CHANNEL_ID)
|
||||
.SetStyle(style)
|
||||
.SetContentTitle(currentTrack.GetString(MediaMetadata.MetadataKeyTitle))
|
||||
.SetContentText(currentTrack.GetString(MediaMetadata.MetadataKeyArtist))
|
||||
.SetSubText(currentTrack.GetString(MediaMetadata.MetadataKeyAlbum))
|
||||
.SetSmallIcon(Resource.Drawable.player_play)
|
||||
.SetSmallIcon(Resource.Drawable.abc_ab_share_pack_mtrl_alpha) //TODO player_play
|
||||
.SetLargeIcon(largeIcon as Bitmap)
|
||||
.SetContentIntent(pendingIntent)
|
||||
.SetShowWhen(false)
|
||||
.SetOngoing(isPlaying)
|
||||
.SetVisibility(NotificationVisibility.Public);
|
||||
|
||||
builder.AddAction(NotificationHelper.GenerateActionCompat(context, Drawable.IcMediaPrevious, "Previous", MediaPlayerService.ActionPrevious));
|
||||
builder.AddAction(GenerateActionCompat(context, Drawable.IcMediaPrevious, "Previous", MediaPlayerService.ActionPrevious));
|
||||
AddPlayPauseActionCompat(builder, context, isPlaying);
|
||||
builder.AddAction(NotificationHelper.GenerateActionCompat(context, Drawable.IcMediaNext, "Next", MediaPlayerService.ActionNext));
|
||||
builder.AddAction(GenerateActionCompat(context, Drawable.IcMediaNext, "Next", MediaPlayerService.ActionNext));
|
||||
style.SetShowActionsInCompactView(0, 1, 2);
|
||||
|
||||
NotificationManagerCompat.From(context).Notify(NotificationId, builder.Build());
|
||||
}
|
||||
|
||||
private static void AddPlayPauseActionCompat(
|
||||
Notification.Builder builder,
|
||||
Builder builder,
|
||||
Context context,
|
||||
bool isPlaying)
|
||||
{
|
||||
if (isPlaying)
|
||||
builder.AddAction(NotificationHelper.GenerateActionCompat(context, Drawable.IcMediaPause, "Pause", MediaPlayerService.ActionPause));
|
||||
builder.AddAction(GenerateActionCompat(context, Drawable.IcMediaPause, "Pause", MediaPlayerService.ActionPause));
|
||||
else
|
||||
builder.AddAction(NotificationHelper.GenerateActionCompat(context, Drawable.IcMediaPlay, "Play", MediaPlayerService.ActionPlay));
|
||||
builder.AddAction(GenerateActionCompat(context, Drawable.IcMediaPlay, "Play", MediaPlayerService.ActionPlay));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Android;
|
||||
|
||||
[BroadcastReceiver(Exported = true)]
|
||||
[IntentFilter(new[] { Intent.ActionMediaButton })]
|
||||
public class RemoteControlBroadcastReceiver : BroadcastReceiver
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// gets the class name for the component
|
||||
/// </summary>
|
||||
/// <value>The name of the component.</value>
|
||||
public string ComponentName { get { return Class.Name; } }
|
||||
|
||||
/// <Docs>The Context in which the receiver is running.</Docs>
|
||||
/// <summary>
|
||||
/// When we receive the action media button intent
|
||||
/// parse the key event and tell our service what to do.
|
||||
/// </summary>
|
||||
/// <param name="context">Context.</param>
|
||||
/// <param name="intent">Intent.</param>
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
if (intent.Action != Intent.ActionMediaButton)
|
||||
return;
|
||||
|
||||
//The event will fire twice, up and down.
|
||||
// we only want to handle the down event though.
|
||||
var key = (KeyEvent)intent.GetParcelableExtra(Intent.ExtraKeyEvent);
|
||||
if (key.Action != KeyEventActions.Down)
|
||||
return;
|
||||
|
||||
string action;
|
||||
|
||||
switch (key.KeyCode)
|
||||
{
|
||||
case Keycode.Headsethook:
|
||||
case Keycode.MediaPlayPause:
|
||||
action = MediaPlayerService.ActionTogglePlayback;
|
||||
break;
|
||||
case Keycode.MediaPlay:
|
||||
action = MediaPlayerService.ActionPlay;
|
||||
break;
|
||||
case Keycode.MediaPause:
|
||||
action = MediaPlayerService.ActionPause;
|
||||
break;
|
||||
case Keycode.MediaStop:
|
||||
action = MediaPlayerService.ActionStop;
|
||||
break;
|
||||
case Keycode.MediaNext:
|
||||
action = MediaPlayerService.ActionNext;
|
||||
break;
|
||||
case Keycode.MediaPrevious:
|
||||
action = MediaPlayerService.ActionPrevious;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var remoteIntent = new Intent(action);
|
||||
context.StartService(remoteIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using AVFoundation;
|
||||
using Foundation;
|
||||
|
||||
namespace SharedMauiLib.Platforms.MacCatalyst;
|
||||
|
||||
public class NativeAudioService : INativeAudioService
|
||||
{
|
||||
AVPlayer avPlayer;
|
||||
string _uri;
|
||||
|
||||
public bool IsPlaying => avPlayer != null
|
||||
? avPlayer.TimeControlStatus == AVPlayerTimeControlStatus.Playing
|
||||
: false;
|
||||
|
||||
public double CurrentPosition => avPlayer?.CurrentTime.Seconds ?? 0;
|
||||
public event EventHandler<bool> IsPlayingChanged;
|
||||
|
||||
public async Task InitializeAsync(string audioURI)
|
||||
{
|
||||
_uri = audioURI;
|
||||
NSUrl fileURL = new NSUrl(_uri.ToString());
|
||||
|
||||
if (avPlayer != null)
|
||||
{
|
||||
await PauseAsync();
|
||||
}
|
||||
|
||||
avPlayer = new AVPlayer(fileURL);
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
avPlayer?.Pause();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlayAsync(double position = 0)
|
||||
{
|
||||
await avPlayer.SeekAsync(new CoreMedia.CMTime((long)position, 1));
|
||||
avPlayer?.Play();
|
||||
}
|
||||
|
||||
public Task SetCurrentTime(double value)
|
||||
{
|
||||
return avPlayer.SeekAsync(new CoreMedia.CMTime((long)value, 1));
|
||||
}
|
||||
|
||||
public Task SetMuted(bool value)
|
||||
{
|
||||
if (avPlayer != null)
|
||||
{
|
||||
avPlayer.Muted = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetVolume(int value)
|
||||
{
|
||||
if (avPlayer != null)
|
||||
{
|
||||
avPlayer.Volume = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
avPlayer?.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
using Windows.Media.Core;
|
||||
using Windows.Media.Playback;
|
||||
|
||||
namespace SharedMauiLib.Platforms.Windows;
|
||||
|
||||
public class NativeAudioService : INativeAudioService
|
||||
{
|
||||
string _uri;
|
||||
MediaPlayer mediaPlayer;
|
||||
|
||||
public bool IsPlaying => mediaPlayer != null
|
||||
&& mediaPlayer.CurrentState == MediaPlayerState.Playing;
|
||||
|
||||
public double CurrentPosition => mediaPlayer?.Position.TotalSeconds ?? 0;
|
||||
public event EventHandler<bool> IsPlayingChanged;
|
||||
|
||||
public async Task InitializeAsync(string audioURI)
|
||||
{
|
||||
_uri = audioURI;
|
||||
|
||||
if (mediaPlayer == null)
|
||||
{
|
||||
mediaPlayer = new MediaPlayer
|
||||
{
|
||||
Source = MediaSource.CreateFromUri(new Uri(_uri)),
|
||||
AudioCategory = MediaPlayerAudioCategory.Media
|
||||
};
|
||||
}
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
await PauseAsync();
|
||||
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(_uri));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
mediaPlayer?.Pause();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task PlayAsync(double position = 0)
|
||||
{
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.Position = TimeSpan.FromSeconds(position);
|
||||
mediaPlayer.Play();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetCurrentTime(double value)
|
||||
{
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.Position = TimeSpan.FromSeconds(value);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetMuted(bool value)
|
||||
{
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.IsMuted = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetVolume(int value)
|
||||
{
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.Volume = value != 0
|
||||
? value / 100d
|
||||
: 0;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
mediaPlayer?.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
using AVFoundation;
|
||||
using Foundation;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.iOS;
|
||||
namespace SharedMauiLib.Platforms.iOS;
|
||||
|
||||
public class AudioService : IAudioService
|
||||
public class NativeAudioService : INativeAudioService
|
||||
{
|
||||
AVPlayer avPlayer;
|
||||
string _uri;
|
||||
|
||||
public bool IsPlaying => avPlayer != null
|
||||
? avPlayer.TimeControlStatus == AVPlayerTimeControlStatus.Playing
|
||||
: false; //TODO
|
||||
: false;
|
||||
|
||||
public double CurrentPosition => avPlayer?.CurrentTime.Seconds ?? 0;
|
||||
public event EventHandler<bool> IsPlayingChanged;
|
||||
|
||||
public async Task InitializeAsync(string audioURI)
|
||||
{
|
||||
|
@ -30,7 +31,7 @@ public class AudioService : IAudioService
|
|||
public Task PauseAsync()
|
||||
{
|
||||
avPlayer?.Pause();
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -39,4 +40,35 @@ public class AudioService : IAudioService
|
|||
await avPlayer.SeekAsync(new CoreMedia.CMTime((long)position, 1));
|
||||
avPlayer?.Play();
|
||||
}
|
||||
}
|
||||
|
||||
public Task SetCurrentTime(double value)
|
||||
{
|
||||
return avPlayer.SeekAsync(new CoreMedia.CMTime((long)value, 1));
|
||||
}
|
||||
|
||||
public Task SetMuted(bool value)
|
||||
{
|
||||
if (avPlayer != null)
|
||||
{
|
||||
avPlayer.Muted = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetVolume(int value)
|
||||
{
|
||||
if (avPlayer != null)
|
||||
{
|
||||
avPlayer.Volume = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
avPlayer?.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 637 B |
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!--<TargetFrameworks>net6.0-android</TargetFrameworks>-->
|
||||
<TargetFrameworks>net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) and '$(MSBuildRuntimeType)' == 'Full'">$(TargetFrameworks);net6.0-windows10.0.19041</TargetFrameworks>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net6.0-ios'">14.2</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net6.0-maccatalyst'">14.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net6.0-android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-windows'))">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$(TargetFramework.Contains('-windows'))">10.0.17763.0</TargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -56,6 +56,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lib\SharedMauiLib\SharedMauiLib.csproj" />
|
||||
<ProjectReference Include="..\Web\Components\Podcast.Components.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
using Android.Media;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.Android;
|
||||
|
||||
public class AudioService : IAudioService
|
||||
{
|
||||
MainActivity instance;
|
||||
|
||||
private MediaPlayer mediaPlayer => (instance != null &&
|
||||
instance.binder.GetMediaPlayerService() != null ) ?
|
||||
instance.binder.GetMediaPlayerService().mediaPlayer : null;
|
||||
|
||||
public bool IsPlaying => mediaPlayer?.IsPlaying ?? false;
|
||||
|
||||
public double CurrentPosition => mediaPlayer?.CurrentPosition/1000 ?? 0;
|
||||
|
||||
public async Task InitializeAsync(string audioURI)
|
||||
{
|
||||
if (this.instance == null)
|
||||
{
|
||||
this.instance = MainActivity.instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.instance.binder.GetMediaPlayerService().Stop();
|
||||
}
|
||||
|
||||
this.instance.binder.GetMediaPlayerService().AudioUrl = audioURI;
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
if (IsPlaying)
|
||||
{
|
||||
return this.instance.binder.GetMediaPlayerService().Pause();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlayAsync(double position = 0)
|
||||
{
|
||||
await this.instance.binder.GetMediaPlayerService().Play();
|
||||
await this.instance.binder.GetMediaPlayerService().Seek((int)position * 1000);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@
|
|||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Microsoft.NetConf2021.Maui.Platforms.Android.Services;
|
||||
using SharedMauiLib.Platforms.Android;
|
||||
using SharedMauiLib.Platforms.Android.CurrentActivity;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui;
|
||||
|
||||
|
@ -10,24 +11,21 @@ namespace Microsoft.NetConf2021.Maui;
|
|||
Theme = "@style/Maui.SplashTheme",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
public class MainActivity : MauiAppCompatActivity , IAudioActivity
|
||||
{
|
||||
internal static MainActivity instance;
|
||||
public MediaPlayerServiceBinder binder;
|
||||
MediaPlayerServiceConnection mediaPlayerServiceConnection;
|
||||
|
||||
public MediaPlayerServiceBinder Binder { get; set; }
|
||||
|
||||
public event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public event CoverReloadedEventHandler CoverReloaded;
|
||||
|
||||
public event PlayingEventHandler Playing;
|
||||
|
||||
public event BufferingEventHandler Buffering;
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
instance = this;
|
||||
CrossCurrentActivity.Current.Init(this, savedInstanceState);
|
||||
NotificationHelper.CreateNotificationChannel(ApplicationContext);
|
||||
if (mediaPlayerServiceConnection == null)
|
||||
InitializeMedia();
|
||||
|
@ -39,32 +37,4 @@ public class MainActivity : MauiAppCompatActivity
|
|||
var mediaPlayerServiceIntent = new Intent(ApplicationContext, typeof(MediaPlayerService));
|
||||
BindService(mediaPlayerServiceIntent, mediaPlayerServiceConnection, Bind.AutoCreate);
|
||||
}
|
||||
|
||||
class MediaPlayerServiceConnection : Java.Lang.Object, IServiceConnection
|
||||
{
|
||||
readonly MainActivity instance;
|
||||
|
||||
public MediaPlayerServiceConnection(MainActivity mediaPlayer)
|
||||
{
|
||||
this.instance = mediaPlayer;
|
||||
}
|
||||
|
||||
public void OnServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
if (service is MediaPlayerServiceBinder binder)
|
||||
{
|
||||
instance.binder = binder;
|
||||
|
||||
var mediaPlayerService = binder.GetMediaPlayerService();
|
||||
mediaPlayerService.CoverReloaded += (object sender, EventArgs e) => { instance.CoverReloaded?.Invoke(sender, e); };
|
||||
mediaPlayerService.StatusChanged += (object sender, EventArgs e) => { instance.StatusChanged?.Invoke(sender, e); };
|
||||
mediaPlayerService.Playing += (object sender, EventArgs e) => { instance.Playing?.Invoke(sender, e); };
|
||||
mediaPlayerService.Buffering += (object sender, EventArgs e) => { instance.Buffering?.Invoke(sender, e); };
|
||||
}
|
||||
}
|
||||
|
||||
public void OnServiceDisconnected(ComponentName name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
using Microsoft.NetConf2021.Maui.Platforms.Android.Services;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.Android.Receivers;
|
||||
|
||||
[BroadcastReceiver(Exported = true)]
|
||||
[IntentFilter(new[] { Intent.ActionMediaButton })]
|
||||
public class RemoteControlBroadcastReceiver : BroadcastReceiver
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// gets the class name for the component
|
||||
/// </summary>
|
||||
/// <value>The name of the component.</value>
|
||||
public string ComponentName { get { return this.Class.Name; } }
|
||||
|
||||
/// <Docs>The Context in which the receiver is running.</Docs>
|
||||
/// <summary>
|
||||
/// When we receive the action media button intent
|
||||
/// parse the key event and tell our service what to do.
|
||||
/// </summary>
|
||||
/// <param name="context">Context.</param>
|
||||
/// <param name="intent">Intent.</param>
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
if (intent.Action != Intent.ActionMediaButton)
|
||||
return;
|
||||
|
||||
//The event will fire twice, up and down.
|
||||
// we only want to handle the down event though.
|
||||
var key = (KeyEvent)intent.GetParcelableExtra(Intent.ExtraKeyEvent);
|
||||
if (key.Action != KeyEventActions.Down)
|
||||
return;
|
||||
|
||||
string action;
|
||||
|
||||
switch (key.KeyCode)
|
||||
{
|
||||
case Keycode.Headsethook:
|
||||
case Keycode.MediaPlayPause:
|
||||
action = MediaPlayerService.ActionTogglePlayback;
|
||||
break;
|
||||
case Keycode.MediaPlay:
|
||||
action = MediaPlayerService.ActionPlay;
|
||||
break;
|
||||
case Keycode.MediaPause:
|
||||
action = MediaPlayerService.ActionPause;
|
||||
break;
|
||||
case Keycode.MediaStop:
|
||||
action = MediaPlayerService.ActionStop;
|
||||
break;
|
||||
case Keycode.MediaNext:
|
||||
action = MediaPlayerService.ActionNext;
|
||||
break;
|
||||
case Keycode.MediaPrevious:
|
||||
action = MediaPlayerService.ActionPrevious;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var remoteIntent = new Intent(action);
|
||||
context.StartService(remoteIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
using AVFoundation;
|
||||
using Foundation;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.MacCatalyst;
|
||||
|
||||
public class AudioService : IAudioService
|
||||
{
|
||||
AVPlayer avPlayer;
|
||||
string _uri;
|
||||
|
||||
public bool IsPlaying => avPlayer != null
|
||||
? avPlayer.TimeControlStatus == AVPlayerTimeControlStatus.Playing
|
||||
: false; //TODO
|
||||
|
||||
public double CurrentPosition => avPlayer?.CurrentTime.Seconds ?? 0;
|
||||
|
||||
public async Task InitializeAsync(string audioURI)
|
||||
{
|
||||
_uri = audioURI;
|
||||
NSUrl fileURL = new NSUrl(_uri.ToString());
|
||||
|
||||
if (avPlayer != null)
|
||||
{
|
||||
await PauseAsync();
|
||||
}
|
||||
|
||||
avPlayer = new AVPlayer(fileURL);
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
avPlayer?.Pause();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlayAsync(double position = 0)
|
||||
{
|
||||
await avPlayer.SeekAsync(new CoreMedia.CMTime((long)position, 1));
|
||||
avPlayer?.Play();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
using Windows.Media.Core;
|
||||
using Windows.Media.Playback;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Platforms.Windows;
|
||||
|
||||
public class AudioService : IAudioService
|
||||
{
|
||||
string _uri;
|
||||
MediaPlayer mediaPlayer;
|
||||
|
||||
public bool IsPlaying => mediaPlayer != null
|
||||
&& mediaPlayer.CurrentState == MediaPlayerState.Playing;
|
||||
|
||||
public double CurrentPosition => (long)mediaPlayer?.Position.TotalSeconds;
|
||||
|
||||
public async Task InitializeAsync(string audioURI)
|
||||
{
|
||||
_uri = audioURI;
|
||||
|
||||
if(this.mediaPlayer == null)
|
||||
{
|
||||
this.mediaPlayer = new MediaPlayer
|
||||
{
|
||||
Source = MediaSource.CreateFromUri(new Uri(_uri)),
|
||||
AudioCategory = MediaPlayerAudioCategory.Media
|
||||
};
|
||||
}
|
||||
if (this.mediaPlayer != null)
|
||||
{
|
||||
await PauseAsync();
|
||||
this.mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(_uri));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
this.mediaPlayer?.Pause();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task PlayAsync(double position = 0)
|
||||
{
|
||||
if (this.mediaPlayer != null)
|
||||
{
|
||||
mediaPlayer.Position = TimeSpan.FromSeconds(position);
|
||||
mediaPlayer.Play();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components",
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Podcast.Components", "..\Web\Components\Podcast.Components.csproj", "{39AD8B68-B8D3-43D4-86D4-3589E4AE81AD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedMauiLib", "..\Lib\SharedMauiLib\SharedMauiLib.csproj", "{772830C6-56B3-4A57-A8E1-50A715F149F5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -31,6 +33,10 @@ Global
|
|||
{39AD8B68-B8D3-43D4-86D4-3589E4AE81AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{39AD8B68-B8D3-43D4-86D4-3589E4AE81AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{39AD8B68-B8D3-43D4-86D4-3589E4AE81AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{772830C6-56B3-4A57-A8E1-50A715F149F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{772830C6-56B3-4A57-A8E1-50A715F149F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{772830C6-56B3-4A57-A8E1-50A715F149F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{772830C6-56B3-4A57-A8E1-50A715F149F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
namespace Microsoft.NetConf2021.Maui.Services;
|
||||
|
||||
public interface IAudioService
|
||||
{
|
||||
Task InitializeAsync(string audioURI);
|
||||
Task PlayAsync(double position = 0);
|
||||
Task PauseAsync();
|
||||
bool IsPlaying { get; }
|
||||
double CurrentPosition { get; }
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
namespace Microsoft.NetConf2021.Maui.Services;
|
||||
using SharedMauiLib;
|
||||
|
||||
namespace Microsoft.NetConf2021.Maui.Services;
|
||||
|
||||
public class PlayerService
|
||||
{
|
||||
private readonly IAudioService audioService;
|
||||
private readonly INativeAudioService audioService;
|
||||
private readonly WifiOptionsService wifiOptionsService;
|
||||
|
||||
public Episode CurrentEpisode { get; set; }
|
||||
|
@ -14,10 +16,16 @@ public class PlayerService
|
|||
public event EventHandler NewEpisodeAdded;
|
||||
public event EventHandler IsPlayingChanged;
|
||||
|
||||
public PlayerService(IAudioService audioService, WifiOptionsService wifiOptionsService)
|
||||
public PlayerService(INativeAudioService audioService, WifiOptionsService wifiOptionsService)
|
||||
{
|
||||
this.audioService = audioService;
|
||||
this.wifiOptionsService = wifiOptionsService;
|
||||
|
||||
this.audioService.IsPlayingChanged += (object sender, bool e) =>
|
||||
{
|
||||
IsPlaying = e;
|
||||
IsPlayingChanged?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
}
|
||||
|
||||
public async Task PlayAsync(Episode episode, Show show, bool isPlaying, double position = 0)
|
||||
|
@ -39,27 +47,13 @@ public class PlayerService
|
|||
|
||||
await audioService.InitializeAsync(CurrentEpisode.Url.ToString());
|
||||
|
||||
if (isPlaying)
|
||||
{
|
||||
await InternalPlayAsync(initializePlayer: false, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InternalPauseAsync();
|
||||
}
|
||||
await InternalPlayPauseAsync(isPlaying, position);
|
||||
|
||||
NewEpisodeAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPlaying)
|
||||
{
|
||||
await InternalPlayAsync(initializePlayer: false, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InternalPauseAsync();
|
||||
}
|
||||
await InternalPlayPauseAsync(isPlaying, position);
|
||||
}
|
||||
|
||||
IsPlayingChanged?.Invoke(this, EventArgs.Empty);
|
||||
|
@ -75,13 +69,25 @@ public class PlayerService
|
|||
return PlayAsync(episode, show, isPlaying, position);
|
||||
}
|
||||
|
||||
private async Task InternalPlayPauseAsync(bool isPlaying, double position)
|
||||
{
|
||||
if (isPlaying)
|
||||
{
|
||||
await InternalPlayAsync(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InternalPauseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InternalPauseAsync()
|
||||
{
|
||||
await audioService.PauseAsync();
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
private async Task InternalPlayAsync(bool initializePlayer = false, double position = 0)
|
||||
private async Task InternalPlayAsync(double position = 0)
|
||||
{
|
||||
var canPlay = await wifiOptionsService.HasWifiOrCanPlayWithOutWifiAsync();
|
||||
|
||||
|
@ -90,11 +96,6 @@ public class PlayerService
|
|||
return;
|
||||
}
|
||||
|
||||
if (initializePlayer)
|
||||
{
|
||||
await audioService.InitializeAsync(CurrentEpisode.Url.ToString());
|
||||
}
|
||||
|
||||
await audioService.PlayAsync(position);
|
||||
IsPlaying = true;
|
||||
}
|
||||
|
|
|
@ -11,14 +11,14 @@ public static class ServicesExtensions
|
|||
builder.Services.AddSingleton<ShowsService>();
|
||||
builder.Services.AddSingleton<ListenLaterService>();
|
||||
#if WINDOWS
|
||||
builder.Services.TryAddSingleton<IAudioService, Platforms.Windows.AudioService>();
|
||||
builder.Services.TryAddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.Windows.NativeAudioService>();
|
||||
#elif ANDROID
|
||||
builder.Services.TryAddSingleton<IAudioService, Platforms.Android.AudioService>();
|
||||
builder.Services.TryAddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.Android.NativeAudioService>();
|
||||
#elif MACCATALYST
|
||||
builder.Services.TryAddSingleton<IAudioService, Platforms.MacCatalyst.AudioService>();
|
||||
builder.Services.TryAddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.MacCatalyst.NativeAudioService>();
|
||||
builder.Services.TryAddSingleton< Platforms.MacCatalyst.ConnectivityService>();
|
||||
#elif IOS
|
||||
builder.Services.TryAddSingleton<IAudioService, Platforms.iOS.AudioService>();
|
||||
builder.Services.TryAddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.iOS.NativeAudioService>();
|
||||
#endif
|
||||
|
||||
builder.Services.TryAddTransient<WifiOptionsService>();
|
||||
|
|
|
@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Podcast.Pages", "..\Web\Pag
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Podcast.Components", "..\Web\Components\Podcast.Components.csproj", "{BFA06A28-839A-4324-87EE-2FF6B8D522A2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedMauiLib", "..\Lib\SharedMauiLib\SharedMauiLib.csproj", "{4A34B6DA-AE7F-4C6B-B8A8-CDC14A2CA178}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -38,6 +40,10 @@ Global
|
|||
{BFA06A28-839A-4324-87EE-2FF6B8D522A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFA06A28-839A-4324-87EE-2FF6B8D522A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFA06A28-839A-4324-87EE-2FF6B8D522A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4A34B6DA-AE7F-4C6B-B8A8-CDC14A2CA178}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A34B6DA-AE7F-4C6B-B8A8-CDC14A2CA178}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A34B6DA-AE7F-4C6B-B8A8-CDC14A2CA178}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A34B6DA-AE7F-4C6B-B8A8-CDC14A2CA178}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.WebView.Maui;
|
||||
using NetPodsMauiBlazor.Services;
|
||||
using Podcast.Components;
|
||||
using Podcast.Pages.Data;
|
||||
using Podcast.Shared;
|
||||
|
@ -25,8 +25,19 @@ public static class MauiProgram
|
|||
{
|
||||
client.BaseAddress = new Uri(APIUrl);
|
||||
});
|
||||
|
||||
#if WINDOWS
|
||||
builder.Services.AddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.Windows.NativeAudioService>();
|
||||
#elif ANDROID
|
||||
builder.Services.AddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.Android.NativeAudioService>();
|
||||
#elif MACCATALYST
|
||||
builder.Services.AddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.MacCatalyst.NativeAudioService>();
|
||||
#elif IOS
|
||||
builder.Services.AddSingleton<SharedMauiLib.INativeAudioService, SharedMauiLib.Platforms.iOS.NativeAudioService>();
|
||||
#endif
|
||||
|
||||
builder.Services.AddScoped<ThemeInterop>();
|
||||
builder.Services.AddScoped<AudioInterop>();
|
||||
builder.Services.AddScoped<IAudioInterop, AudioInteropService>();
|
||||
builder.Services.AddScoped<LocalStorageInterop>();
|
||||
builder.Services.AddScoped<ClipboardInterop>();
|
||||
builder.Services.AddScoped<SubscriptionsService>();
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Lib\SharedMauiLib\SharedMauiLib.csproj" />
|
||||
<ProjectReference Include="..\..\Web\Pages\Podcast.Pages.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true" android:networkSecurityConfig="@xml/network_security_config"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
</manifest>
|
|
@ -1,10 +1,37 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Microsoft.Maui;
|
||||
using Android.OS;
|
||||
using SharedMauiLib.Platforms.Android;
|
||||
using SharedMauiLib.Platforms.Android.CurrentActivity;
|
||||
|
||||
namespace NetPodsMauiBlazor;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
public class MainActivity : MauiAppCompatActivity, IAudioActivity
|
||||
{
|
||||
MediaPlayerServiceConnection mediaPlayerServiceConnection;
|
||||
|
||||
public MediaPlayerServiceBinder Binder { get; set; }
|
||||
|
||||
public event StatusChangedEventHandler StatusChanged;
|
||||
public event CoverReloadedEventHandler CoverReloaded;
|
||||
public event PlayingEventHandler Playing;
|
||||
public event BufferingEventHandler Buffering;
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
CrossCurrentActivity.Current.Init(this, savedInstanceState);
|
||||
NotificationHelper.CreateNotificationChannel(ApplicationContext);
|
||||
if (mediaPlayerServiceConnection == null)
|
||||
InitializeMedia();
|
||||
}
|
||||
|
||||
private void InitializeMedia()
|
||||
{
|
||||
mediaPlayerServiceConnection = new MediaPlayerServiceConnection(this);
|
||||
var mediaPlayerServiceIntent = new Intent(ApplicationContext, typeof(MediaPlayerService));
|
||||
BindService(mediaPlayerServiceIntent, mediaPlayerServiceConnection, Bind.AutoCreate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
using SharedMauiLib;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Podcast.Components;
|
||||
using Podcast.Pages.Data;
|
||||
using System.Timers;
|
||||
|
||||
namespace NetPodsMauiBlazor.Services;
|
||||
|
||||
internal class AudioInteropService : IAudioInterop
|
||||
{
|
||||
private readonly INativeAudioService _nativeAudioService;
|
||||
private readonly PlayerService _playerService;
|
||||
private readonly System.Timers.Timer currentTimeTimer;
|
||||
|
||||
public AudioInteropService(
|
||||
INativeAudioService nativeAudioService,
|
||||
PlayerService playerService)
|
||||
{
|
||||
_nativeAudioService = nativeAudioService;
|
||||
_playerService = playerService;
|
||||
|
||||
currentTimeTimer = new System.Timers.Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
|
||||
currentTimeTimer.Elapsed += OnCurrentTimeEvent;
|
||||
}
|
||||
|
||||
public Task Pause(ElementReference element)
|
||||
{
|
||||
return _nativeAudioService.PauseAsync();
|
||||
}
|
||||
|
||||
public async Task Play(ElementReference element)
|
||||
{
|
||||
await _nativeAudioService.PlayAsync(_nativeAudioService.CurrentPosition);
|
||||
|
||||
}
|
||||
|
||||
public async Task SetCurrentTime(ElementReference element, double value)
|
||||
{
|
||||
await _nativeAudioService.SetCurrentTime(value);
|
||||
}
|
||||
|
||||
public Task SetMuted(ElementReference element, bool value)
|
||||
{
|
||||
return _nativeAudioService.SetMuted(value);
|
||||
}
|
||||
|
||||
public void SetUri(string audioURI)
|
||||
{
|
||||
if (audioURI != null)
|
||||
{
|
||||
_nativeAudioService.InitializeAsync(audioURI).Wait();
|
||||
currentTimeTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public Task SetVolume(ElementReference element, int value)
|
||||
{
|
||||
return _nativeAudioService.SetVolume(value);
|
||||
}
|
||||
|
||||
public Task Stop(ElementReference element)
|
||||
{
|
||||
return _nativeAudioService.PauseAsync();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
currentTimeTimer.Dispose();
|
||||
return _nativeAudioService.DisposeAsync();
|
||||
}
|
||||
|
||||
private void OnCurrentTimeEvent(Object source, ElapsedEventArgs e)
|
||||
{
|
||||
_playerService.CurrentTime = _nativeAudioService.CurrentPosition;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ builder.Services.AddHttpClient<PodcastService>(client =>
|
|||
client.BaseAddress = new Uri(builder.Configuration["PodcastApi:BaseAddress"]!);
|
||||
});
|
||||
builder.Services.AddScoped<ThemeInterop>();
|
||||
builder.Services.AddScoped<AudioInterop>();
|
||||
builder.Services.AddScoped<IAudioInterop, AudioInterop>();
|
||||
builder.Services.AddScoped<LocalStorageInterop>();
|
||||
builder.Services.AddScoped<ClipboardInterop>();
|
||||
builder.Services.AddScoped<SubscriptionsService>();
|
||||
|
|
|
@ -3,7 +3,7 @@ using Microsoft.JSInterop;
|
|||
|
||||
namespace Podcast.Components;
|
||||
|
||||
public class AudioInterop : IAsyncDisposable
|
||||
public class AudioInterop : IAudioInterop ,IAsyncDisposable
|
||||
{
|
||||
private readonly Lazy<Task<IJSObjectReference>> moduleTask;
|
||||
|
||||
|
@ -57,4 +57,9 @@ public class AudioInterop : IAsyncDisposable
|
|||
await module.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUri(string? audioURI)
|
||||
{
|
||||
//no action is necessary
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Podcast.Components;
|
||||
|
||||
public interface IAudioInterop
|
||||
{
|
||||
void SetUri(string? audioURI);
|
||||
|
||||
Task Play(ElementReference element);
|
||||
|
||||
Task Pause(ElementReference element);
|
||||
|
||||
Task Stop(ElementReference element);
|
||||
|
||||
Task SetMuted(ElementReference element, bool value);
|
||||
|
||||
Task SetVolume(ElementReference element, int value);
|
||||
|
||||
Task SetCurrentTime(ElementReference element, double value);
|
||||
|
||||
ValueTask DisposeAsync();
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
@implements IDisposable
|
||||
@inject PlayerService PlayerService
|
||||
@inject AudioInterop AudioJsInterop
|
||||
@inject IAudioInterop AudioJsInterop
|
||||
|
||||
<audio @ref="AudioElementRef"
|
||||
src="@url"
|
||||
|
@ -25,6 +25,7 @@
|
|||
PlayerService.TimeSought += OnTimeSought;
|
||||
|
||||
url = PlayerService.Episode?.Url;
|
||||
AudioJsInterop.SetUri(url);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -112,6 +113,7 @@
|
|||
{
|
||||
url = newValue;
|
||||
loadingUrl = true;
|
||||
AudioJsInterop.SetUri(url);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ builder.Services.AddHttpClient<PodcastService>(client =>
|
|||
client.BaseAddress = new Uri(builder.Configuration["PodcastApi:BaseAddress"]!);
|
||||
});
|
||||
builder.Services.AddScoped<ThemeInterop>();
|
||||
builder.Services.AddScoped<AudioInterop>();
|
||||
builder.Services.AddScoped<IAudioInterop, AudioInterop>();
|
||||
builder.Services.AddScoped<LocalStorageInterop>();
|
||||
builder.Services.AddScoped<ClipboardInterop>();
|
||||
builder.Services.AddScoped<SubscriptionsService>();
|
||||
|
|
Loading…
Reference in New Issue