Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion MauiAudio.Sample/MainPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ void Play()
async void PlayInStream()
{
var stream = await FileSystem.OpenAppPackageFileAsync("sample.mp3");
await playerService.PlayAsync(new() { Stream=stream, Name = "TempUrl", Author = "TempAuthor" });
var img = await GetCoverImage();
await playerService.PlayAsync(new() { Stream=stream, Name = "TempUrl", Author = "TempAuthor", ImageBytes = img, DurationInMs = 188000 });
}

private void OnTimeChanging(object sender, EventArgs e)
Expand All @@ -56,4 +57,13 @@ async void StopPlay()
{
await playerService.dispose();
}
async Task<byte[]>? GetCoverImage(string filePath = null)
{
using var imageStream = await FileSystem.OpenAppPackageFileAsync("shadow.png");

using var memStream = new MemoryStream();
imageStream.CopyTo(memStream);
//doing return File.ReadAllBytes(IMAGE_FULL_PATH_AS_STRING); Will work as well
return memStream.ToArray();
}
}
14 changes: 11 additions & 3 deletions MauiAudio.Sample/MauiAudio.Sample.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
<RootNamespace>MauiAudio.Sample</RootNamespace>
<UseMaui>true</UseMaui>
Expand Down Expand Up @@ -48,6 +48,10 @@
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<None Remove="Resources\Images\shadow.png" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="1.3.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0" />
Expand All @@ -61,4 +65,8 @@
<Folder Include="Services\" />
</ItemGroup>

<ItemGroup>
<MauiImage Include="Resources\Raw\shadow.png" />
</ItemGroup>

</Project>
Binary file added MauiAudio.Sample/Resources/Raw/shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions MauiAudio/MauiAudio.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks>net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</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)' == 'net8.0-ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net8.0-maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net8.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>
<Version>1.2.2</Version>
Expand All @@ -36,13 +36,13 @@
<ExcludeFromCurrentConfiguration>true</ExcludeFromCurrentConfiguration>
</Compile>
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-ios')) != true AND $(TargetFramework.StartsWith('net6.0-maccatalyst')) != true">
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true AND $(TargetFramework.StartsWith('net8.0-maccatalyst')) != true">
<Compile Remove="**\*.macios.cs" />
<None Include="**\*.macios.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<Compile Remove="**\macios\**\*.cs" />
<None Include="**\macios\**\*.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-android')) != true ">
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-android')) != true ">
<Compile Remove="**\*.android.cs" />
<None Include="**\*.android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<Compile Remove="**\android\**\*.cs" />
Expand Down
8 changes: 8 additions & 0 deletions MauiAudio/MediaPlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,13 @@ public class MediaPlay
public string URL { get; set; }
public Stream Stream { get; set; }
public string Image { get; set; }
/// <summary>
/// Get/Set Album Cover in Byte[] to put on Notification
/// </summary>
public byte[] ImageBytes { get; set; }
/// <summary>
/// Get/Set the VALUE of the Duration, solely used for the Notification bar to show Progress.
/// </summary>
public long DurationInMs { get; set; } = 0;
}
}
18 changes: 18 additions & 0 deletions MauiAudio/NativeAudioService.windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,28 @@ private MediaPlaybackItem mediaPlaybackItem(MediaPlay media)
if (media.Name != null) props.MusicProperties.Title = media.Name;
if (media.Author != null) props.MusicProperties.Artist = media.Author;
if (media.Image != null)
{
props.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri(media.Image));
}
else if(media.ImageBytes != null)
{
props.Thumbnail = ConvertToRandomAccessStreamReference(media.ImageBytes);
}

mediaItem.ApplyDisplayProperties(props);
return mediaItem;
}

private RandomAccessStreamReference ConvertToRandomAccessStreamReference(byte[] imageData)
{
using var stream = new MemoryStream(imageData);
var randomAccessStream = new InMemoryRandomAccessStream();
var writer = new DataWriter(randomAccessStream.GetOutputStreamAt(0));
writer.WriteBytes(imageData);
writer.StoreAsync().GetResults();
return RandomAccessStreamReference.CreateFromStream(randomAccessStream);
}

public async Task InitializeAsync(MediaPlay media)
{
if (mediaPlayer == null)
Expand Down
81 changes: 59 additions & 22 deletions MauiAudio/Platforms/Android/MediaPlayerService.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Media;
using Android.Media.Session;
using Android.Net;
using Android.Net.Wifi;
using Android.OS;
using Android.Media.Session;
using AndroidNet = Android.Net;
using Android.Graphics;

namespace MauiAudio.Platforms.Android;


[Service(Exported = true)]
[IntentFilter(new[] { ActionPlay, ActionPause, ActionStop, ActionTogglePlayback, ActionNext, ActionPrevious })]
[IntentFilter(new[] { ActionPlay, ActionPause, ActionStop, ActionTogglePlayback, ActionNext, ActionPrevious, ActionSeekTo })]
public class MediaPlayerService : Service,
AudioManager.IOnAudioFocusChangeListener,
MediaPlayer.IOnBufferingUpdateListener,
Expand All @@ -27,6 +27,7 @@ public class MediaPlayerService : Service,
public const string ActionTogglePlayback = "com.xamarin.action.TOGGLEPLAYBACK";
public const string ActionNext = "com.xamarin.action.NEXT";
public const string ActionPrevious = "com.xamarin.action.PREVIOUS";
public const string ActionSeekTo = "com.xamarin.action.ActionSeekTo";

public MediaPlayer mediaPlayer;
private AudioManager audioManager;
Expand Down Expand Up @@ -104,7 +105,7 @@ protected virtual void OnStatusChanged(EventArgs e)
protected virtual void OnPlayingChanged(bool e)
{
PlayingChanged?.Invoke(this, e);
IsPlayingChanged?.Invoke(this,e);
IsPlayingChanged?.Invoke(this, e);
}

protected virtual void OnCoverReloaded(EventArgs e)
Expand Down Expand Up @@ -279,8 +280,8 @@ public object Cover
set
{
cover = value as Bitmap;
if(cover!=null)
OnCoverReloaded(EventArgs.Empty);
if (cover != null)
OnCoverReloaded(EventArgs.Empty);
}
}

Expand Down Expand Up @@ -327,18 +328,18 @@ private async Task PrepareAndPlayMediaPlayerAsync()
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();

AndroidNet.Uri uri;
if(mediaPlay.Stream!= null)
if (mediaPlay.Stream != null)
{
var fileStream = File.Create(FileSystem.Current.CacheDirectory+"temp.wav");
var fileStream = File.Create(FileSystem.Current.CacheDirectory + "temp.wav");
mediaPlay.Stream.CopyTo(fileStream);
fileStream.Close();
uri = AndroidNet.Uri.Parse( FileSystem.Current.CacheDirectory + "temp.wav");
uri = AndroidNet.Uri.Parse(FileSystem.Current.CacheDirectory + "temp.wav");
}
else
{
uri = AndroidNet.Uri.Parse(mediaPlay.URL);
}
await mediaPlayer.SetDataSourceAsync(ApplicationContext, uri);
await mediaPlayer.SetDataSourceAsync(ApplicationContext, uri);

//If Uri Scheme is not set its a local file so there's no metadata to fetch
if (!string.IsNullOrWhiteSpace(uri.Scheme))
Expand Down Expand Up @@ -367,6 +368,10 @@ private async Task PrepareAndPlayMediaPlayerAsync()
{
Cover = await GetImageBitmapFromUrl(mediaPlay.Image);
}
else if (mediaPlay.ImageBytes is not null)
{
Cover = await GetImageBitmapFromUrlBytesAsync(mediaPlay.ImageBytes);
}
else if (metaRetriever != null && !string.IsNullOrWhiteSpace(metaRetriever.ExtractMetadata(MetadataKey.Album)))
{
byte[] imageByteArray = metaRetriever.GetEmbeddedPicture();
Expand Down Expand Up @@ -403,6 +408,17 @@ private async Task<Bitmap> GetImageBitmapFromUrl(string url)

return imageBitmap;
}
async Task<Bitmap> GetImageBitmapFromUrlBytesAsync(byte[] imageBytes)
{
Bitmap imageBitmap = null;
if (imageBytes != null && imageBytes.Length > 0)
{

imageBitmap = await BitmapFactory.DecodeByteArrayAsync(imageBytes, 0, imageBytes.Length);
}

return imageBitmap;
}
public async Task Seek(int position)
{
await Task.Run(() =>
Expand All @@ -416,7 +432,7 @@ await Task.Run(() =>

public async Task PlayNext()
{
TaskPlayNext?.Invoke(this,EventArgs.Empty);
TaskPlayNext?.Invoke(this, EventArgs.Empty);
//if (mediaPlayer != null)
//{
// mediaPlayer.Reset();
Expand Down Expand Up @@ -511,11 +527,14 @@ public void UpdatePlaybackStateStopped()
}
}

private void UpdatePlaybackState(PlaybackStateCode state)
private void UpdatePlaybackState(PlaybackStateCode state, int SeekedPosition = 0)
{
if (mediaSession == null || mediaPlayer == null)
return;

if (SeekedPosition == 0)
{
SeekedPosition = Position;
}
try
{
PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
Expand All @@ -525,9 +544,10 @@ private void UpdatePlaybackState(PlaybackStateCode state)
PlaybackState.ActionPlayPause |
PlaybackState.ActionSkipToNext |
PlaybackState.ActionSkipToPrevious |
PlaybackState.ActionStop
PlaybackState.ActionStop |
PlaybackState.ActionSeekTo
)
.SetState(state, Position, 1.0f, SystemClock.ElapsedRealtime());
.SetState(state, SeekedPosition, 1.0f, SystemClock.ElapsedRealtime());

mediaSession.SetPlaybackState(stateBuilder.Build());

Expand All @@ -548,7 +568,6 @@ private void StartNotification()
{
if (mediaSession == null)
return;

NotificationHelper.StartNotification(
ApplicationContext,
mediaController.Metadata,
Expand All @@ -558,7 +577,7 @@ private void StartNotification()
}

/// <summary>
/// Updates the metadata on the lock screen
/// Updates the metadata on the lock screen and on notification bar
/// </summary>
private void UpdateMediaMetadataCompat(MediaMetadataRetriever metaRetriever = null)
{
Expand All @@ -570,16 +589,20 @@ private void UpdateMediaMetadataCompat(MediaMetadataRetriever metaRetriever = nu
if (metaRetriever != null)
{
builder
.PutString(MediaMetadata.MetadataKeyAlbum,metaRetriever.ExtractMetadata(MetadataKey.Album))
.PutString(MediaMetadata.MetadataKeyAlbum, metaRetriever.ExtractMetadata(MetadataKey.Album))
.PutString(MediaMetadata.MetadataKeyArtist, mediaPlay.Author ?? metaRetriever.ExtractMetadata(MetadataKey.Artist))
.PutString(MediaMetadata.MetadataKeyTitle, mediaPlay.Name ?? metaRetriever.ExtractMetadata(MetadataKey.Title));
.PutString(MediaMetadata.MetadataKeyTitle, mediaPlay.Name ?? metaRetriever.ExtractMetadata(MetadataKey.Title))
.PutLong(MediaMetadata.MetadataKeyDuration, mediaPlay.DurationInMs);
// using this metaRetriever.ExtractMetadata(MetadataKey.Duration)) doesn't work
}
else
{
builder
.PutString(MediaMetadata.MetadataKeyAlbum, mediaSession.Controller.Metadata.GetString(MediaMetadata.MetadataKeyAlbum))
.PutString(MediaMetadata.MetadataKeyArtist, mediaSession.Controller.Metadata.GetString(MediaMetadata.MetadataKeyArtist))
.PutString(MediaMetadata.MetadataKeyTitle, mediaSession.Controller.Metadata.GetString(MediaMetadata.MetadataKeyTitle));
.PutString(MediaMetadata.MetadataKeyTitle, mediaSession.Controller.Metadata.GetString(MediaMetadata.MetadataKeyTitle))
.PutLong(MediaMetadata.MetadataKeyDuration, mediaSession.Controller.Metadata.GetLong(MediaMetadata.MetadataKeyDuration));

}
builder.PutBitmap(MediaMetadata.MetadataKeyAlbumArt, Cover as Bitmap);

Expand Down Expand Up @@ -619,6 +642,10 @@ private void HandleIntent(Intent intent)
{
mediaController.GetTransportControls().Stop();
}
else if (action.Equals(ActionSeekTo))
{
mediaController.GetTransportControls().SeekTo(Position);
}
}

/// <summary>
Expand Down Expand Up @@ -731,17 +758,19 @@ public MediaSessionCallback(MediaPlayerServiceBinder service)
{
mediaPlayerService = service;
}

bool isPlaying = true;
public override async void OnPause()
{
mediaPlayerService.GetMediaPlayerService().OnPlayingChanged(false);
base.OnPause();
isPlaying = false;
}

public override async void OnPlay()
{
mediaPlayerService.GetMediaPlayerService().OnPlayingChanged(true);
base.OnPlay();
isPlaying = true;
}

public override async void OnSkipToNext()
Expand All @@ -761,13 +790,21 @@ public override async void OnStop()
await mediaPlayerService.GetMediaPlayerService().Stop();
base.OnStop();
}
public override async void OnSeekTo(long position)
{

await mediaPlayerService.GetMediaPlayerService().Seek((int)position);
mediaPlayerService.GetMediaPlayerService().UpdatePlaybackState( isPlaying == true ? PlaybackStateCode.Playing : PlaybackStateCode.Paused
, (int)position);
base.OnSeekTo(position);
}
}
}

public class MediaPlayerServiceBinder : Binder
{
private readonly MediaPlayerService service;

public MediaPlayerServiceBinder(MediaPlayerService service)
{
this.service = service;
Expand Down