// Copyright (c) Arlo Godfrey. All Rights Reserved.
// Licensed under the GNU Lesser General Public License, Version 3.0 with additional terms.
// See the LICENSE, LICENSE.LESSER and LICENSE.ADDITIONAL files in the project root for more information.
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using StrixMusic.Sdk.AppModels;
using StrixMusic.Sdk.BaseModels;
using StrixMusic.Sdk.CoreModels;
using StrixMusic.Sdk.Extensions;
namespace StrixMusic.Sdk.MediaPlayback
{
///
/// Test
///
public partial class PlaybackHandlerService
{
///
public async Task PlayAsync(ITrackCollection trackCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var apiTracks = await trackCollection.GetTracksAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);
Guard.HasSizeGreaterThan(apiTracks, 0, nameof(apiTracks));
await PlayAsync(apiTracks[0], trackCollection, context, cancellationToken);
}
///
public async Task PlayAsync(ITrack track, ITrackCollection trackCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var canPlay = await PrepareToPlayCollection(cancellationToken);
if (!canPlay)
return;
ClearPrevious();
ClearNext();
await AddTrackCollectionToQueue(track, trackCollection, cancellationToken: cancellationToken);
if (ShuffleState)
ShuffleOnInternal();
CurrentItemContext = context;
await PlayFromNext(0, cancellationToken);
}
///
public async Task PlayAsync(IArtistCollection artistCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var apiArtists = await artistCollection.GetArtistItemsAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);
Guard.HasSizeGreaterThan(apiArtists, 0, nameof(apiArtists));
await PlayAsync(apiArtists[0], artistCollection, context, cancellationToken);
}
///
public async Task PlayAsync(IArtistCollectionItem artistCollectionItem, IArtistCollection artistCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var canPlay = await PrepareToPlayCollection(cancellationToken);
if (!canPlay)
{
await artistCollection.PlayArtistCollectionAsync(artistCollectionItem, cancellationToken);
return;
}
ClearPrevious();
ClearNext();
await AddArtistCollectionToQueue(artistCollectionItem, artistCollection, cancellationToken);
if (ShuffleState)
ShuffleOnInternal();
CurrentItemContext = context;
await PlayFromNext(0, cancellationToken);
}
///
public async Task PlayAsync(IPlaylistCollection playlistCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var apiPlaylists = await playlistCollection.GetPlaylistItemsAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);
Guard.HasSizeGreaterThan(apiPlaylists, 0, nameof(apiPlaylists));
await PlayAsync(apiPlaylists[0], playlistCollection, context, cancellationToken);
}
///
public async Task PlayAsync(IAlbumCollection albumCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var apiItems = await albumCollection.GetAlbumItemsAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);
Guard.HasSizeGreaterThan(apiItems, 0, nameof(apiItems));
await PlayAsync(apiItems[0], albumCollection, context, cancellationToken);
}
///
public async Task PlayAsync(IAlbumCollectionItem albumCollectionItem, IAlbumCollection albumCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var canPlay = await PrepareToPlayCollection(cancellationToken);
if (!canPlay)
{
await albumCollection.PlayAlbumCollectionAsync(albumCollectionItem, cancellationToken);
return;
}
ClearPrevious();
ClearNext();
await AddAlbumCollectionToQueue(albumCollectionItem, albumCollection, cancellationToken);
if (ShuffleState)
ShuffleOnInternal();
CurrentItemContext = context;
await PlayFromNext(0, cancellationToken);
}
///
public async Task PlayAsync(IPlaylistCollectionItem playlistCollectionItem, IPlaylistCollection playlistCollection, IPlayableBase context, CancellationToken cancellationToken = default)
{
var canPlay = await PrepareToPlayCollection(cancellationToken);
if (!canPlay)
{
await playlistCollection.PlayPlaylistCollectionAsync(playlistCollectionItem, cancellationToken);
return;
}
ClearPrevious();
ClearNext();
await AddPlaylistCollectionToQueue(playlistCollectionItem, playlistCollection, cancellationToken);
if (ShuffleState)
ShuffleOnInternal();
CurrentItemContext = context;
await PlayFromNext(0, cancellationToken);
}
///
/// Common tasks done by all "Play" methods when playback is requested.
///
/// True if playback should continue locally, false if playback should continue remotely.
private async Task PrepareToPlayCollection(CancellationToken cancellationToken = default)
{
// Pause the active player first.
if (ActiveDevice is not null)
await ActiveDevice.PauseAsync(cancellationToken);
// If there is no active device, activate the device used for local playback.
if (ActiveDevice is null)
{
ActiveDevice = _localDevice;
await _localDevice.SwitchToAsync(cancellationToken);
}
Guard.IsNotNull(ActiveDevice, nameof(ActiveDevice));
// If the active device is controlled remotely, the rest is handled there.
return ActiveDevice.Type != DeviceType.Remote;
}
///
/// Adds the tracks in the collection to the queue.
///
/// A target track to return an index for.
/// The collection to iterate for adding to the queue.
/// When adding to the queue, specify where to push the items to.
/// A cancellation token that may be used to cancel the ongoing task.
/// The index of the given in the .
private Task AddTrackCollectionToQueue(ITrack track, ITrackCollection trackCollection, AddTrackPushTarget pushTarget = AddTrackPushTarget.Normal, CancellationToken cancellationToken = default) => AddTrackCollectionToQueue(track, trackCollection, 0, pushTarget, cancellationToken);
///
/// Adds the tracks in the collection to the queue.
///
/// A target track to return an index for.
/// The collection to iterate for adding to the queue.
/// The offset to add when adding tracks to .
/// When adding to the queue, specify where to push the items to.
/// A cancellation token that may be used to cancel the ongoing task.
/// The index of the given in the .
private async Task AddTrackCollectionToQueue(ITrack track, ITrackCollection trackCollection, int offset, AddTrackPushTarget pushTarget = AddTrackPushTarget.Normal, CancellationToken cancellationToken = default)
{
// Setup for local playback
var trackPlaybackIndex = 0;
var reachedTargetTrack = false;
var i = -1;
await foreach (var item in trackCollection.GetTracksAsync(trackCollection.TotalTrackCount, 0, cancellationToken))
{
i++;
var coreTrack = item.GetSources().First(x => x.Id == item.Id);
var mediaSource = await coreTrack.SourceCore.GetMediaSourceAsync(coreTrack, cancellationToken);
if (mediaSource is null)
continue;
if (item.Id == track.Id)
{
reachedTargetTrack = true;
trackPlaybackIndex = i;
}
var playbackItem = new PlaybackItem
{
MediaConfig = mediaSource,
Track = item
};
switch (pushTarget)
{
case AddTrackPushTarget.Normal when reachedTargetTrack:
InsertNext(i - trackPlaybackIndex, playbackItem);
break;
case AddTrackPushTarget.Normal:
case AddTrackPushTarget.AllPrevious:
PushPrevious(playbackItem);
break;
case AddTrackPushTarget.AllNext:
InsertNext(i + offset, playbackItem);
break;
default:
return ThrowHelper.ThrowArgumentOutOfRangeException(nameof(pushTarget));
}
}
Guard.IsTrue(reachedTargetTrack, nameof(reachedTargetTrack));
return trackPlaybackIndex;
}
///
/// Adds all albums in the given collection to the queue.
///
/// The instance and index of the first playable track in the selected items within the entire .
private async Task<(ITrack PlaybackTrack, int Index)> AddAlbumCollectionToQueue(IAlbumCollectionItem? albumCollectionItem, IAlbumCollection albumCollection, CancellationToken cancellationToken = default)
{
var itemIndex = 0;
ITrack? playbackTrack = null;
var foundItemTarget = false;
var offset = 0;
await foreach (var albumItem in albumCollection.GetAlbumItemsAsync(albumCollection.TotalAlbumItemsCount, offset, cancellationToken))
{
if (albumItem is IAlbum album)
{
// Ignore albums without any tracks
if (album.TotalTrackCount < 1)
continue;
var tracks = await album.GetTracksAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);;
var firstTrack = tracks[0];
if (albumItem.Id == albumCollectionItem?.Id && !foundItemTarget)
{
// Tracks are added to the queue of previous items until we reach the item the user wants to play.
itemIndex = _prevItems.Count;
foundItemTarget = true;
playbackTrack = firstTrack;
}
if (foundItemTarget)
{
_ = await AddTrackCollectionToQueue(firstTrack, album, offset,
foundItemTarget ? AddTrackPushTarget.AllNext : AddTrackPushTarget.AllPrevious, cancellationToken);
offset = _nextItems.Count;
}
else
{
_ = await AddTrackCollectionToQueue(firstTrack, album,
foundItemTarget ? AddTrackPushTarget.AllNext : AddTrackPushTarget.AllPrevious, cancellationToken);
}
}
if (albumItem is IAlbumCollection albumCol)
{
_ = await AddAlbumCollectionToQueue(null, albumCol, cancellationToken);
}
}
Guard.IsTrue(foundItemTarget, nameof(foundItemTarget));
Guard.IsNotNull(playbackTrack, nameof(playbackTrack));
return (playbackTrack, itemIndex);
}
///
/// Adds all artists in the given collection to the queue.
///
/// The instance and index of the first playable track in the selected items within the entire .
private async Task<(ITrack PlaybackTrack, int Index)> AddArtistCollectionToQueue(IArtistCollectionItem? artistCollectionItem, IArtistCollection artistCollection, CancellationToken cancellationToken = default)
{
var itemIndex = 0;
ITrack? playbackTrack = null;
var foundItemTarget = false;
var offset = 0;
await foreach (var artistItem in artistCollection.GetArtistItemsAsync(artistCollection.TotalArtistItemsCount, offset, cancellationToken))
{
switch (artistItem)
{
// Ignore artist without any tracks
case IArtist { TotalTrackCount: < 1 }:
continue;
case IArtist artist:
var tracks = await artist.GetTracksAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);
var firstTrack = tracks[0];
if (artistItem.Id == artistCollectionItem?.Id && !foundItemTarget)
{
// Tracks are added to the queue of previous items until we reach the item the user wants to play.
itemIndex = _prevItems.Count;
foundItemTarget = true;
playbackTrack = firstTrack;
}
if (foundItemTarget)
{
_ = await AddTrackCollectionToQueue(firstTrack, artist, offset,
foundItemTarget ? AddTrackPushTarget.AllNext : AddTrackPushTarget.AllPrevious, cancellationToken);
offset = _nextItems.Count;
}
else
{
_ = await AddTrackCollectionToQueue(firstTrack, artist,
foundItemTarget ? AddTrackPushTarget.AllNext : AddTrackPushTarget.AllPrevious, cancellationToken);
}
break;
case IArtistCollection artistCol:
_ = await AddArtistCollectionToQueue(null, artistCol, cancellationToken);
break;
}
}
Guard.IsTrue(foundItemTarget, nameof(foundItemTarget));
Guard.IsNotNull(playbackTrack, nameof(playbackTrack));
return (playbackTrack, itemIndex);
}
///
/// Adds all playlists in the given collection to the queue.
///
/// The instance and index of the first playable track in the selected items within the entire .
private async Task<(ITrack PlaybackTrack, int Index)> AddPlaylistCollectionToQueue(IPlaylistCollectionItem? playlistCollectionItem, IPlaylistCollection playlistCollection, CancellationToken cancellationToken = default)
{
var itemIndex = 0;
ITrack? playbackTrack = null;
var foundItemTarget = false;
var offset = 0;
await foreach (var playlistItem in playlistCollection.GetPlaylistItemsAsync(playlistCollection.TotalPlaylistItemsCount, offset, cancellationToken))
{
if (playlistItem is IPlaylist playlist)
{
// Ignore artist without any tracks
if (playlist.TotalTrackCount < 1)
continue;
var tracks = await playlist.GetTracksAsync(1, 0, cancellationToken).ToListAsync(cancellationToken);
var firstTrack = tracks[0];
if (playlistItem.Id == playlistCollectionItem?.Id && !foundItemTarget)
{
// Tracks are added to the queue of previous items until we reach the item the user wants to play.
itemIndex = _prevItems.Count;
foundItemTarget = true;
playbackTrack = firstTrack;
}
if (foundItemTarget)
{
_ = await AddTrackCollectionToQueue(firstTrack, playlist, offset,
foundItemTarget ? AddTrackPushTarget.AllNext : AddTrackPushTarget.AllPrevious, cancellationToken);
offset = _nextItems.Count;
}
else
{
_ = await AddTrackCollectionToQueue(firstTrack, playlist,
foundItemTarget ? AddTrackPushTarget.AllNext : AddTrackPushTarget.AllPrevious, cancellationToken);
}
}
if (playlistItem is IPlaylistCollection playlistCol)
{
_ = await AddPlaylistCollectionToQueue(null, playlistCol, cancellationToken);
}
}
Guard.IsTrue(foundItemTarget, nameof(foundItemTarget));
Guard.IsNotNull(playbackTrack, nameof(playbackTrack));
return (playbackTrack, itemIndex);
}
private enum AddTrackPushTarget : byte
{
Normal = 0,
AllPrevious = 1,
AllNext = 2,
}
}
}