Skip to content

Commit

Permalink
add share track button
Browse files Browse the repository at this point in the history
  • Loading branch information
zznty committed Oct 17, 2024
1 parent 627490c commit 182c7b1
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 35 deletions.
1 change: 1 addition & 0 deletions MusicX.Shared/Player/PlaylistTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public sealed record BoomTrackData(string Url, bool IsLiked, bool IsExplicit, Ti
public record IdInfo(long Id, long OwnerId, string AccessKey)
{
public string ToOwnerIdString() => $"{OwnerId}_{Id}";
public override string ToString() => $"{OwnerId}_{Id}_{AccessKey}";
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)]
Expand Down
8 changes: 8 additions & 0 deletions MusicX/Controls/TrackControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
Margin="10,0,0,0"
/>
</StackPanel>

<StackPanel MouseDown="Share_MouseDown" Orientation="Horizontal">
<iconElements:SymbolIcon Symbol="Share20" />
<TextBlock
Margin="10,0,0,0"

Text="Поделиться" />
</StackPanel>

<MenuItem
x:Name="GoToArtistMenu"
Expand Down
7 changes: 7 additions & 0 deletions MusicX/Controls/TrackControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,5 +601,12 @@ private void MainGrid_OnMouseLeave(object sender, MouseEventArgs e)
ExplicitBadgeColumn.Width = GridLength.Auto;
TimeColumn.Width = GridLength.Auto;
}

private void Share_MouseDown(object sender, MouseButtonEventArgs e)
{
var shareService = StaticService.Container.GetRequiredService<ShareService>();

shareService.ShareTrack(Audio.ToTrack());
}
}
}
66 changes: 66 additions & 0 deletions MusicX/Helpers/EmbeddedFileStreamReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage.Streams;

namespace MusicX.Helpers;

public class EmbeddedFileStreamReference(string resourceName, string contentType) : IRandomAccessStreamReference
{
public IAsyncOperation<IRandomAccessStreamWithContentType> OpenReadAsync() =>
OpenReadAsyncInternal().AsAsyncOperation();

private async Task<IRandomAccessStreamWithContentType> OpenReadAsyncInternal()
{
var assembly = typeof(EmbeddedFileStreamReference).Assembly;
await using var manifestResourceStream = assembly.GetManifestResourceStream(resourceName);

if (manifestResourceStream == null)
throw new FileNotFoundException("Resource not found", resourceName);

var stream = new InMemoryRandomAccessStream();

await using var writeStream = stream.AsStreamForWrite();
await manifestResourceStream.CopyToAsync(writeStream);

return new InMemoryRandomAccessStreamWithContentType(stream, contentType);
}

}

public sealed class InMemoryRandomAccessStreamWithContentType(
InMemoryRandomAccessStream randomAccessStream,
string contentType) : IRandomAccessStreamWithContentType
{
public void Dispose()
{
randomAccessStream.Dispose();
}

public IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) =>
randomAccessStream.ReadAsync(buffer, count, options);

public IAsyncOperationWithProgress<uint, uint> WriteAsync(IBuffer buffer) =>
randomAccessStream.WriteAsync(buffer);

public IAsyncOperation<bool> FlushAsync() => randomAccessStream.FlushAsync();

public IInputStream GetInputStreamAt(ulong position) => randomAccessStream.GetInputStreamAt(position);

public IOutputStream GetOutputStreamAt(ulong position) => randomAccessStream.GetOutputStreamAt(position);

public void Seek(ulong position) => randomAccessStream.Seek(position);

public IRandomAccessStream CloneStream() => randomAccessStream.CloneStream();

public bool CanRead => randomAccessStream.CanRead;
public bool CanWrite => randomAccessStream.CanWrite;
public ulong Position => randomAccessStream.Position;
public ulong Size
{
get => randomAccessStream.Size;
set => randomAccessStream.Size = value;
}
public string ContentType => contentType;
}
25 changes: 25 additions & 0 deletions MusicX/Helpers/IDataTransferManagerInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Runtime.InteropServices;
using Windows.ApplicationModel.DataTransfer;
using WinRT;

namespace MusicX.Helpers;

public static class DataTransferManagerInterop
{
private static IDataTransferManagerInterop InteropInstance => DataTransferManager.As<IDataTransferManagerInterop>();

public static DataTransferManager GetForWindow(nint appWindow) => DataTransferManager.FromAbi(InteropInstance
.GetForWindow(appWindow, new("A5CAEE9B-8708-49D1-8D36-67D25A8DA00C") /* Guid of IDataTransferManager */));

public static void ShowForWindow(nint appWindow) => InteropInstance
.ShowShareUIForWindow(appWindow);

[ComImport, Guid("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IDataTransferManagerInterop
{
nint GetForWindow([In] nint appWindow, [In] ref Guid riid);
void ShowShareUIForWindow(nint appWindow);
}
}
2 changes: 2 additions & 0 deletions MusicX/MusicX.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@

<Resource Include="Assets\icons\ic_fluent_video_play_pause_24_regular.png" />

<EmbeddedResource Include="StoreLogo.scale-30.png" />

<Resource Include="StoreLogo.scale-400.png" />

<None Include="Shaders\ColorOverlayShader.hlsl" />
Expand Down
4 changes: 4 additions & 0 deletions MusicX/RootWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ private async void Window_Loaded(object sender, RoutedEventArgs e)
}

this.WindowState = WindowState.Normal;

var shareService = StaticService.Container.GetRequiredService<ShareService>();

shareService.AssignWindow(new(this));
}
catch (Exception ex)
{
Expand Down
75 changes: 41 additions & 34 deletions MusicX/Services/DownloaderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,44 +50,12 @@ public string GetDownloadDirectoryAsync()
return Directory.CreateDirectory(directory).FullName;
}

public async Task DownloadAudioAsync(PlaylistTrack audio, IProgress<(TimeSpan Position, TimeSpan Duration)>? progress = null, CancellationToken cancellationToken = default)
public async Task DownloadAudioAsync(PlaylistTrack audio, IProgress<(TimeSpan Position, TimeSpan Duration)>? progress = null, FileInfo? destinationFile = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(audio.Data.Url))
return;

var fileName = $"{audio.GetArtistsString()} - {audio.Title}";
fileName = ReplaceSymbols(fileName) + ".mp3";

string fileDownloadPath;
var musicFolder = GetDownloadDirectoryAsync();

if (audio.Data is DownloaderData data)
{
var name = ReplaceSymbols(data.PlaylistName);

var playlistDirPath = Path.Combine(musicFolder, name);

if (!Directory.Exists(playlistDirPath))
{
Directory.CreateDirectory(playlistDirPath);
}

fileDownloadPath = Path.Combine(playlistDirPath, fileName);
}
else
{
fileDownloadPath = Path.Combine(musicFolder, fileName);
}

var i = 0;
while (File.Exists(fileDownloadPath))
{
fileDownloadPath = fileDownloadPath.Replace(".mp3", string.Empty);
var value = $"({i})";
if (fileDownloadPath.EndsWith(value))
fileDownloadPath = fileDownloadPath[..^value.Length];
fileDownloadPath += $"({++i}).mp3";
}
var fileDownloadPath = destinationFile?.FullName ?? ResolveFileDownloadPath(audio);

if (audio.Data is BoomTrackData)
{
Expand Down Expand Up @@ -144,6 +112,45 @@ await _mediaTranscoder.PrepareMediaStreamSourceTranscodeAsync(streamSource,
await AddMetadataAsync(audio, fileDownloadPath, cancellationToken);
}

private string ResolveFileDownloadPath(PlaylistTrack audio)
{
var fileName = $"{audio.GetArtistsString()} - {audio.Title}";
fileName = ReplaceSymbols(fileName) + ".mp3";

string fileDownloadPath;
var musicFolder = GetDownloadDirectoryAsync();

if (audio.Data is DownloaderData data)
{
var name = ReplaceSymbols(data.PlaylistName);

var playlistDirPath = Path.Combine(musicFolder, name);

if (!Directory.Exists(playlistDirPath))
{
Directory.CreateDirectory(playlistDirPath);
}

fileDownloadPath = Path.Combine(playlistDirPath, fileName);
}
else
{
fileDownloadPath = Path.Combine(musicFolder, fileName);
}

var i = 0;
while (File.Exists(fileDownloadPath))
{
fileDownloadPath = fileDownloadPath.Replace(".mp3", string.Empty);
var value = $"({i})";
if (fileDownloadPath.EndsWith(value))
fileDownloadPath = fileDownloadPath[..^value.Length];
fileDownloadPath += $"({++i}).mp3";
}

return fileDownloadPath;
}

private string ReplaceSymbols(string fileName)
{
return string.Join("_", fileName.Split(Path.GetInvalidFileNameChars())).Replace('.', '_');
Expand Down
95 changes: 95 additions & 0 deletions MusicX/Services/ShareService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Interop;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Streams;
using MusicX.Helpers;
using MusicX.Services.Player.Playlists;
using MusicX.Shared.Player;
using NLog;
using Wpf.Ui;
using Wpf.Ui.Extensions;

namespace MusicX.Services;

public class ShareService(Logger logger, DownloaderService downloaderService, ISnackbarService snackbarService)
{
private (PlaylistTrack track, FileInfo file)? _pendingTrack;
private WindowInteropHelper? _window;

public void AssignWindow(WindowInteropHelper window)
{
_window = window;

var manager = DataTransferManagerInterop.GetForWindow(window.Handle);

Check failure on line 27 in MusicX/Services/ShareService.cs

View workflow job for this annotation

GitHub Actions / Build and Package

'DataTransferManagerInterop' is an ambiguous reference between 'MusicX.Helpers.DataTransferManagerInterop' and 'Windows.ApplicationModel.DataTransfer.DataTransferManagerInterop'

manager.DataRequested += TransferManagerOnDataRequested;
}

private async void TransferManagerOnDataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
var deferral = args.Request.GetDeferral();

try
{
var data = args.Request.Data;

SetApplicationDetails(data);

if (_pendingTrack is not null)
{
var (track, file) = _pendingTrack.Value;
await SetTrackAsync(data, track, file);
}
else
throw new InvalidOperationException("No pending data to share");
}
catch (Exception e)
{
logger.Error(e, "Failed to respond to a share request");

args.Request.FailWithDisplayText("Упс! Что-то пошло не так");
}
finally
{
deferral.Complete();
}
}

private async Task SetTrackAsync(DataPackage data, PlaylistTrack track, FileInfo file)
{
data.Properties.Title = $"{track.GetArtistsString()} - {track.Title}";
if (track.AlbumId?.CoverUrl is not null)
data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri(track.AlbumId.CoverUrl));

// todo url-only option
// if (track.Data is VkTrackData trackData)
// data.SetWebLink(new Uri($"https://vk.com/audio{trackData.Info}"));

data.SetStorageItems([await StorageFile.GetFileFromPathAsync(file.FullName)]);
}

private static void SetApplicationDetails(DataPackage data)
{
data.Properties.ApplicationName = "MusicX Player";
data.Properties.Square30x30Logo = new EmbeddedFileStreamReference("MusicX.StoreLogo.scale-30.png", "image/png");
}

public async void ShareTrack(PlaylistTrack track)
{
snackbarService.Show("Подождите...", "Мы готовим трек для отправки", TimeSpan.FromSeconds(5));

var file = new FileInfo(Path.Join(Directory.CreateTempSubdirectory("MusicX").FullName, $"{track.GetArtistsString()} - {track.Title}.mp3"));

// todo fix ffmpeg blocking thread on start
await Task.Run(() => downloaderService.DownloadAudioAsync(track, destinationFile: file));

_pendingTrack = (track, file);

if (_window is not null)
DataTransferManagerInterop.ShowForWindow(_window.Handle);

Check failure on line 93 in MusicX/Services/ShareService.cs

View workflow job for this annotation

GitHub Actions / Build and Package

'DataTransferManagerInterop' is an ambiguous reference between 'MusicX.Helpers.DataTransferManagerInterop' and 'Windows.ApplicationModel.DataTransfer.DataTransferManagerInterop'
}
}
Binary file added MusicX/StoreLogo.scale-30.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion MusicX/ViewModels/DownloaderViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ private async Task DownloaderTask(CancellationToken token)
try
{
CurrentDownloadingAudio = audio;
await downloaderService.DownloadAudioAsync(audio, progress, token);
await downloaderService.DownloadAudioAsync(audio, progress, cancellationToken: token);
DownloadProgress = 0;
}
catch (Exception e) when (e is TypeInitializationException or COMException)
Expand Down
1 change: 1 addition & 0 deletions MusicX/Views/StartingWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ await Task.Run(async () =>
collection.AddSingleton(s => new BackendConnectionService(s.GetRequiredService<Logger>(), StaticService.Version));
collection.AddSingleton<WindowThemeService>();
collection.AddSingleton<SectionEventService>();
collection.AddSingleton<ShareService>();

var container = StaticService.Container = collection.BuildServiceProvider();

Expand Down

0 comments on commit 182c7b1

Please sign in to comment.