Skip to content

Commit

Permalink
Merge pull request #402 from Fooxboy/401
Browse files Browse the repository at this point in the history
#401 Переделать алгоритм перемешивания плейлистов
  • Loading branch information
Fooxboy authored Jul 13, 2024
2 parents c47a863 + 32548fb commit 51360b2
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 14 deletions.
6 changes: 5 additions & 1 deletion MusicX/Services/Player/Playlists/IPlaylist.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public abstract class PlaylistBase<TData> : IPlaylist<TData> where TData : class

public bool Equals(IPlaylist? other)
{
return other is PlaylistBase<TData> { Data: { } otherData } && Data.Equals(otherData);
return other is PlaylistBase<TData> { Data: { } otherData } && GetType() == other.GetType() && Data.Equals(otherData);
}

public override bool Equals(object? obj) => Equals((IPlaylist?)obj);
Expand Down Expand Up @@ -68,6 +68,7 @@ public class PlaylistJsonConverter : JsonConverter<IPlaylist>
"radio" => JsonSerializer.Deserialize<RadioPlaylist>(ref reader, options),
"vkBlock" => JsonSerializer.Deserialize<VkBlockPlaylist>(ref reader, options),
"vkPlaylist" => JsonSerializer.Deserialize<VkPlaylistPlaylist>(ref reader, options),
"shuffleVkPlaylist" => JsonSerializer.Deserialize<ShuffleVkPlaylistPlaylist>(ref reader, options),
_ => throw new JsonException("Unsupported playlist type.")
};

Expand Down Expand Up @@ -105,6 +106,9 @@ void WriteObject<T>(string type, T data)
case VkPlaylistPlaylist playlist:
WriteObject("vkPlaylist", playlist);
break;
case ShuffleVkPlaylistPlaylist playlist:
WriteObject("shuffleVkPlaylist", playlist);
break;
default:
throw new JsonException("Unsupported playlist type.");
}
Expand Down
144 changes: 144 additions & 0 deletions MusicX/Services/Player/Playlists/ShuffleVkPlaylistPlaylist.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using MusicX.Core.Models;
using MusicX.Core.Services;
using MusicX.Shared.Player;
using MusicX.ViewModels;

namespace MusicX.Services.Player.Playlists;

[JsonConverter(typeof(PlaylistJsonConverter<ShuffleVkPlaylistPlaylist, PlaylistData>))]
public class ShuffleVkPlaylistPlaylist : PlaylistBase<PlaylistData>
{
private readonly VkService _vkService;
private int _offset;
private int _count;

private ChunkedCollection<PlaylistTrack>? _tracks;

private const int LoadCount = 40;

public ShuffleVkPlaylistPlaylist(VkService vkService, PlaylistData data)
{
_vkService = vkService;
Data = data;
}

public override async IAsyncEnumerable<PlaylistTrack> LoadAsync()
{
var (id, ownerId, accessKey) = Data;
var trackPlaylist = new Playlist
{
Id = Data.PlaylistId,
OwnerId = Data.OwnerId,
AccessKey = Data.AccessKey
};

if (_firstLoad)
{
var playlist = await _vkService.GetPlaylistAsync(LoadCount, id, accessKey, ownerId);

_count = (int)playlist.Playlist.Count;
_tracks = new(_count, async range =>
{
var (offset, length) = range.GetOffsetAndLength(_count);
var response = await _vkService.AudioGetAsync(id, ownerId, accessKey, offset, length + 1);
return response.Items.Select(audio => audio.ToTrack(trackPlaylist));
});

_firstLoad = false;
}

var items = await _tracks!.GetRangeAsync(_offset..Math.Min(_count - 1, _offset + LoadCount));

_offset += Math.Min(_count - _offset - 1, LoadCount);

foreach (var item in items)
{
yield return item;
}
}

private bool _firstLoad = true;
public override bool CanLoad => _firstLoad || _offset < _count;
public override PlaylistData Data { get; }

private class ChunkedCollection<T>
{
public int Count { get; }

private readonly Func<Range, ValueTask<IEnumerable<T>>> _loader;
private readonly int[] _indices;
private readonly T?[] _items;

public ChunkedCollection(int count, Func<Range, ValueTask<IEnumerable<T>>> loader)
{
Count = count;
_loader = loader;

_indices = new int[count];
_items = new T?[count];

for (var i = 0; i < count - 1; i++)
{
_indices[i] = Random.Shared.Next(i, count);
}
}

public async ValueTask<IEnumerable<T>> GetRangeAsync(Range range)
{
var (offset, length) = range.GetOffsetAndLength(Count);

var indicesSegment = new ArraySegment<int>(_indices, offset, length);
var result = new T[length];

for (var i = 0; i < indicesSegment.Count; i++)
{
var index = indicesSegment[i];

if (_items[index] is null)
{
var rangeToLoad = GetLoadableRangeFromIndex(index);

var items = await _loader(rangeToLoad);

var (loadOffset, loadLength) = rangeToLoad.GetOffsetAndLength(Count);

Array.Copy(items as T[] ?? items.ToArray(), 0, _items, loadOffset, loadLength);
}

result[i] = _items[index] ?? throw new InvalidOperationException();
}

return result;
}

private Range GetLoadableRangeFromIndex(int index)
{
var startIndex = Math.Min(index,
Array.FindIndex(_items, Math.Clamp(index - 20, 0, Count - 1), static b => b != null));
int endIndex;

if (startIndex - index > 15)
{
endIndex = startIndex;
startIndex = index;
}
else
{
endIndex = Math.Min(index + 20,
Array.FindIndex(_items, Math.Clamp(index + 1, 0, Count - 1), static b => b != null));
}

if (endIndex < 0 && startIndex < 0)
{
return Math.Clamp(index - 20, 0, Count - 1)..Math.Clamp(index + 20, 0, Count - 1);
}

return startIndex..Math.Max(endIndex, Math.Clamp(index + 20, 0, Count - 1));
}
}
}
32 changes: 19 additions & 13 deletions MusicX/Views/PlaylistView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<controls:Card
x:Name="CardPlaylist"
MinHeight="350"
Margin="0,10,15,10"
Margin="0,10,0,10"
RenderTransformOrigin="0.5,0.5">
<controls:Card.RenderTransform>
<TransformGroup>
Expand Down Expand Up @@ -213,18 +213,6 @@
<controls:SymbolIcon Symbol="Add28"/>
</controls:Button.Icon>
</controls:Button>
<controls:Button
x:Name="PlayPlaylist"
MinWidth="150"
Margin="10,0,0,0"
Appearance="Secondary"
Click="PlayPlaylist_Click"
Content="Воспроизвести"
Foreground="White">
<controls:Button.Icon>
<controls:SymbolIcon Symbol="Play20"/>
</controls:Button.Icon>
</controls:Button>

<controls:Button
x:Name="EditPlaylist"
Expand Down Expand Up @@ -264,6 +252,24 @@
</Grid>
</StackPanel>
</controls:Card>
<controls1:LoadingBorder IsLoading="{Binding IsLoading}" Margin="0,0,0,10" HorizontalAlignment="Stretch">
<Grid Visibility="{Binding IsLoaded, Converter={StaticResource BooleanToHiddenVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:CardAction x:Name="PlayPlaylist" Content="Воспроизвести" IsChevronVisible="False" Click="PlayPlaylist_Click" Margin="0,0,10,0">
<ui:CardAction.Icon>
<controls:SymbolIcon Symbol="Play20"/>
</ui:CardAction.Icon>
</ui:CardAction>
<ui:CardAction Grid.Column="1" Content="Перемешать" IsChevronVisible="False" Click="PlayPlaylistShuffle_Click">
<ui:CardAction.Icon>
<controls:SymbolIcon Symbol="ArrowShuffle20"/>
</ui:CardAction.Icon>
</ui:CardAction>
</Grid>
</controls1:LoadingBorder>
<Grid>
<blocks:AudiosListControl Margin="0,0,0,45" Audios="{Binding Tracks}"/>
<StackPanel Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"
Expand Down
20 changes: 20 additions & 0 deletions MusicX/Views/PlaylistView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,5 +295,25 @@ private void Button_Click(object sender, RoutedEventArgs e)
UseShellExecute = true
});
}

private async void PlayPlaylistShuffle_Click(object sender, RoutedEventArgs e)
{
try
{
var player = StaticService.Container.GetRequiredService<PlayerService>();

_nowPlay = true;

PlayPlaylist.Content = "Остановить воспроизведение";
PlayPlaylist.Icon = new SymbolIcon(SymbolRegular.Pause20);

await player.PlayAsync(new ShuffleVkPlaylistPlaylist(
StaticService.Container.GetRequiredService<VkService>(), ViewModel.PlaylistData));
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
}
}
}
}

0 comments on commit 51360b2

Please sign in to comment.