Добавлен playlist shared
This commit is contained in:
13
YandexMusic/YandexMusic.csproj
Normal file
13
YandexMusic/YandexMusic.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YandexMusic.API\YandexMusic.API.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
517
YandexMusic/YandexMusicClient.cs
Normal file
517
YandexMusic/YandexMusicClient.cs
Normal file
@@ -0,0 +1,517 @@
|
||||
using YandexMusic.API;
|
||||
using YandexMusic.API.Common;
|
||||
using YandexMusic.API.Common.Ynison;
|
||||
using YandexMusic.API.Models.Account;
|
||||
using YandexMusic.API.Models.Album;
|
||||
using YandexMusic.API.Models.Artist;
|
||||
using YandexMusic.API.Models.Common;
|
||||
using YandexMusic.API.Models.Feed;
|
||||
using YandexMusic.API.Models.Landing;
|
||||
using YandexMusic.API.Models.Landing.Entity.Entities.Context;
|
||||
using YandexMusic.API.Models.Library;
|
||||
using YandexMusic.API.Models.Playlist;
|
||||
using YandexMusic.API.Models.Queue;
|
||||
using YandexMusic.API.Models.Radio;
|
||||
using YandexMusic.API.Models.Search;
|
||||
using YandexMusic.API.Models.Track;
|
||||
|
||||
namespace YandexMusic;
|
||||
|
||||
/// <summary>Асинхронный клиент для работы с API Яндекс Музыки.</summary>
|
||||
public class YandexMusicClient : IDisposable
|
||||
{
|
||||
private readonly YandexMusicApi _api;
|
||||
private readonly AuthStorage _storage;
|
||||
private YnisonPlayer? _player;
|
||||
|
||||
/// <summary>Информация об аккаунте (после авторизации).</summary>
|
||||
public YAccount Account => _storage.User;
|
||||
|
||||
/// <summary>Флаг авторизации.</summary>
|
||||
public bool IsAuthorized => _storage.IsAuthorized;
|
||||
|
||||
/// <summary>Плеер Ynison (WebSocket) для синхронизации воспроизведения.</summary>
|
||||
public YnisonPlayer? Ynison => _player;
|
||||
|
||||
/// <summary>Создаёт новый экземпляр клиента Яндекс Музыки.</summary>
|
||||
public YandexMusicClient()
|
||||
{
|
||||
_api = new YandexMusicApi();
|
||||
_storage = new AuthStorage();
|
||||
_player = _api.Ynison.GetPlayer(_storage);
|
||||
}
|
||||
|
||||
#region Авторизация
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация по токену
|
||||
/// </summary>
|
||||
/// <param name="token">Токен авторизации</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> Authorize(string token)
|
||||
{
|
||||
await _api.User.AuthorizeAsync(_storage, token);
|
||||
return _storage.IsAuthorized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание сеанса и получение доступных методов авторизации
|
||||
/// </summary>
|
||||
/// <param name="userName">Имя пользователя</param>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthTypes> CreateAuthSession(string userName)
|
||||
{
|
||||
return _api.User.CreateAuthSessionAsync(_storage, userName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение ссылки на QR-код
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<string> GetAuthQRLink()
|
||||
{
|
||||
return _api.User.GetAuthQRLinkAsync(_storage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация по QR-коду
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthQRStatus> AuthorizeByQR()
|
||||
{
|
||||
return _api.User.AuthorizeByQRAsync(_storage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение <see cref="YAuthCaptcha"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthCaptcha> GetCaptcha()
|
||||
{
|
||||
return _api.User.GetCaptchaAsync(_storage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация по captcha
|
||||
/// </summary>
|
||||
/// <param name="captcha">Значение captcha</param>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthBase> AuthorizeByCaptcha(string captcha)
|
||||
{
|
||||
return _api.User.AuthorizeByCaptchaAsync(_storage, captcha);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение письма авторизации на почту пользователя
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthLetter> GetAuthLetter()
|
||||
{
|
||||
return _api.User.GetAuthLetterAsync(_storage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация после подтверждения входа через письмо
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<bool> AuthorizeByLetter()
|
||||
{
|
||||
return _api.User.AuthorizeByLetterAsync(_storage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация с помощью пароля из приложения Яндекс
|
||||
/// </summary>
|
||||
/// <param name="password">Пароль</param>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthBase> AuthorizeByAppPassword(string password)
|
||||
{
|
||||
return _api.User.AuthorizeByAppPasswordAsync(_storage, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение <see cref="YAccessToken"/> после авторизации с помощью QR, e-mail, пароля из приложения
|
||||
/// </summary>
|
||||
public Task<YAccessToken> GetAccessToken()
|
||||
{
|
||||
return _api.User.GetAccessTokenAsync(_storage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение информации о пользователе через логин Яндекса
|
||||
/// </summary>
|
||||
public Task<YLoginInfo> GetLoginInfo()
|
||||
{
|
||||
return _api.User.GetLoginInfoAsync(_storage);
|
||||
}
|
||||
|
||||
#endregion Авторизация
|
||||
|
||||
#region Треки
|
||||
/// <summary>Получает трек по идентификатору.</summary>
|
||||
public async Task<YTrack?> GetTrackAsync(string id)
|
||||
{
|
||||
var response = await _api.Track.GetAsync(_storage, id);
|
||||
return response?.Result?.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>Получает список треков по идентификаторам.</summary>
|
||||
public async Task<List<YTrack>> GetTracksAsync(IEnumerable<string> ids)
|
||||
{
|
||||
var response = await _api.Track.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YTrack>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Альбомы
|
||||
/// <summary>Получает альбом по идентификатору.</summary>
|
||||
public async Task<YAlbum?> GetAlbumAsync(string id)
|
||||
{
|
||||
var response = await _api.Album.GetAsync(_storage, id);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает список альбомов по идентификаторам.</summary>
|
||||
public async Task<List<YAlbum>> GetAlbumsAsync(IEnumerable<string> ids)
|
||||
{
|
||||
var response = await _api.Album.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YAlbum>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Главная страница
|
||||
/// <summary>Получает персональные блоки лендинга.</summary>
|
||||
public async Task<YLanding?> GetLandingAsync(params YLandingBlockType[] blocks)
|
||||
{
|
||||
var response = await _api.Landing.GetAsync(_storage, blocks);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает ленту событий.</summary>
|
||||
public async Task<YFeed?> GetFeedAsync()
|
||||
{
|
||||
var response = await _api.Landing.GetFeedAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает детский лендинг.</summary>
|
||||
public async Task<YChildrenLanding?> GetChildrenLandingAsync()
|
||||
{
|
||||
var response = await _api.Landing.GetChildrenLandingAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Исполнители
|
||||
/// <summary>Получает информацию об исполнителе.</summary>
|
||||
public async Task<YArtistBriefInfo?> GetArtistAsync(string id)
|
||||
{
|
||||
var response = await _api.Artist.GetAsync(_storage, id);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает список исполнителей.</summary>
|
||||
public async Task<List<YArtist>> GetArtistsAsync(IEnumerable<string> ids)
|
||||
{
|
||||
var response = await _api.Artist.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YArtist>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Плейлисты
|
||||
/// <summary>Получает плейлист по пользователю и идентификатору.</summary>
|
||||
public async Task<YPlaylist?> GetPlaylistAsync(string user, string id)
|
||||
{
|
||||
var response = await _api.Playlist.GetAsync(_storage, user, id);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает плейлист по UUID.</summary>
|
||||
public async Task<YPlaylist?> GetPlaylistAsync(string uuid)
|
||||
{
|
||||
var response = await _api.Playlist.GetAsync(_storage, uuid);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает список плейлистов по списку пар (пользователь, идентификатор).</summary>
|
||||
public async Task<List<YPlaylist>> GetPlaylistsAsync(IEnumerable<(string user, string id)> ids)
|
||||
{
|
||||
var response = await _api.Playlist.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YPlaylist>();
|
||||
}
|
||||
|
||||
/// <summary>Получает персональные плейлисты (с главной страницы).</summary>
|
||||
public async Task<List<YPlaylist>> GetPersonalPlaylistsAsync()
|
||||
{
|
||||
var playlists = await _api.Playlist.GetPersonalPlaylistsAsync(_storage);
|
||||
return playlists.Select(r => r.Result).Where(p => p != null).ToList()!;
|
||||
}
|
||||
|
||||
/// <summary>Получает избранные плейлисты.</summary>
|
||||
public async Task<List<YPlaylist>> GetFavoritesAsync()
|
||||
{
|
||||
var response = await _api.Playlist.FavoritesAsync(_storage);
|
||||
return response?.Result ?? new List<YPlaylist>();
|
||||
}
|
||||
|
||||
/// <summary>Получает плейлист «Дежавю».</summary>
|
||||
public async Task<YPlaylist?> GetDejaVuAsync()
|
||||
{
|
||||
var response = await _api.Playlist.DejaVuAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает плейлист «Тайник».</summary>
|
||||
public async Task<YPlaylist?> GetMissedAsync()
|
||||
{
|
||||
var response = await _api.Playlist.MissedAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает плейлист дня.</summary>
|
||||
public async Task<YPlaylist?> GetOfTheDayAsync()
|
||||
{
|
||||
var response = await _api.Playlist.OfTheDayAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает плейлист «Кинопоиск».</summary>
|
||||
public async Task<YPlaylist?> GetKinopoiskAsync()
|
||||
{
|
||||
var response = await _api.Playlist.KinopoiskAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает плейлист «Премьера».</summary>
|
||||
public async Task<YPlaylist?> GetPremiereAsync()
|
||||
{
|
||||
var response = await _api.Playlist.PremiereAsync(_storage);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Создаёт новый плейлист с заданным именем.</summary>
|
||||
public async Task<YPlaylist?> CreatePlaylistAsync(string name)
|
||||
{
|
||||
var response = await _api.Playlist.CreateAsync(_storage, name);
|
||||
return response?.Result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Поиск
|
||||
/// <summary>Выполняет поиск.</summary>
|
||||
public async Task<YSearch?> SearchAsync(string searchText, YSearchType searchType, int page = 0, int pageSize = 20)
|
||||
{
|
||||
var response = await _api.Search.SearchAsync(_storage, searchText, searchType, page, pageSize);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает подсказки для поискового запроса.</summary>
|
||||
public async Task<YSearchSuggest?> GetSearchSuggestionsAsync(string searchText)
|
||||
{
|
||||
var response = await _api.Search.SuggestAsync(_storage, searchText);
|
||||
return response?.Result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Библиотека
|
||||
/// <summary>Получает лайкнутые треки.</summary>
|
||||
public async Task<List<YTrack>> GetLikedTracksAsync()
|
||||
{
|
||||
var likes = await _api.Library.GetLikedTracksAsync(_storage);
|
||||
var ids = likes.Result?.Library.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty<string>();
|
||||
if (ids.Length == 0) return new List<YTrack>();
|
||||
var response = await _api.Track.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YTrack>();
|
||||
}
|
||||
|
||||
/// <summary>Получает дизлайкнутые треки.</summary>
|
||||
public async Task<List<YTrack>> GetDislikedTracksAsync()
|
||||
{
|
||||
var dislikes = await _api.Library.GetDislikedTracksAsync(_storage);
|
||||
var ids = dislikes.Result?.Library.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty<string>();
|
||||
if (ids.Length == 0) return new List<YTrack>();
|
||||
var response = await _api.Track.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YTrack>();
|
||||
}
|
||||
|
||||
/// <summary>Получает лайкнутые альбомы.</summary>
|
||||
public async Task<List<YAlbum>> GetLikedAlbumsAsync()
|
||||
{
|
||||
var albums = await _api.Library.GetLikedAlbumsAsync(_storage);
|
||||
var ids = albums.Result?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
|
||||
if (ids.Length == 0) return new List<YAlbum>();
|
||||
var response = await _api.Album.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YAlbum>();
|
||||
}
|
||||
|
||||
/// <summary>Получает лайкнутых исполнителей.</summary>
|
||||
public async Task<List<YArtist>> GetLikedArtistsAsync()
|
||||
{
|
||||
var artists = await _api.Library.GetLikedArtistsAsync(_storage);
|
||||
var ids = artists.Result?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
|
||||
if (ids.Length == 0) return new List<YArtist>();
|
||||
var response = await _api.Artist.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YArtist>();
|
||||
}
|
||||
|
||||
/// <summary>Получает дизлайкнутых исполнителей.</summary>
|
||||
public async Task<List<YArtist>> GetDislikedArtistsAsync()
|
||||
{
|
||||
var artists = await _api.Library.GetDislikedArtistsAsync(_storage);
|
||||
var ids = artists.Result?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
|
||||
if (ids.Length == 0) return new List<YArtist>();
|
||||
var response = await _api.Artist.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YArtist>();
|
||||
}
|
||||
|
||||
/// <summary>Получает лайкнутые плейлисты.</summary>
|
||||
public async Task<List<YPlaylist>> GetLikedPlaylistsAsync()
|
||||
{
|
||||
var playlists = await _api.Library.GetLikedPlaylistsAsync(_storage);
|
||||
var ids = playlists.Result?
|
||||
.Select(p => (p.Playlist.Uid, p.Playlist.Kind))
|
||||
.ToArray() ?? Array.Empty<(string, string)>();
|
||||
if (ids.Length == 0) return new List<YPlaylist>();
|
||||
var response = await _api.Playlist.GetAsync(_storage, ids);
|
||||
return response?.Result ?? new List<YPlaylist>();
|
||||
}
|
||||
|
||||
/// <summary>Получает список недавно прослушанных треков.</summary>
|
||||
/// <param name="contextTypes">Типы контекстов (альбом, исполнитель, плейлист).</param>
|
||||
/// <param name="trackCount">Количество треков на контекст.</param>
|
||||
/// <param name="contextCount">Количество контекстов.</param>
|
||||
public async Task<List<YRecentlyListened>> GetRecentlyListenedAsync(
|
||||
IEnumerable<YPlayContextType> contextTypes,
|
||||
int trackCount = 50,
|
||||
int contextCount = 10)
|
||||
{
|
||||
var response = await _api.Library.GetRecentlyListenedAsync(_storage, contextTypes, trackCount, contextCount);
|
||||
return response?.Result?.Contexts ?? new List<YRecentlyListened>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Радио
|
||||
/// <summary>Получает список рекомендованных радиостанций.</summary>
|
||||
public async Task<List<YStation>> GetRadioDashboardAsync()
|
||||
{
|
||||
var response = await _api.Radio.GetStationsDashboardAsync(_storage);
|
||||
return response?.Result?.Stations ?? new List<YStation>();
|
||||
}
|
||||
|
||||
/// <summary>Получает список всех радиостанций.</summary>
|
||||
public async Task<List<YStation>> GetRadioStationsAsync()
|
||||
{
|
||||
var response = await _api.Radio.GetStationsAsync(_storage);
|
||||
return response?.Result ?? new List<YStation>();
|
||||
}
|
||||
|
||||
/// <summary>Получает информацию о радиостанции по идентификатору.</summary>
|
||||
public async Task<YStation?> GetRadioStationAsync(YStationId id)
|
||||
{
|
||||
var response = await _api.Radio.GetStationAsync(_storage, id);
|
||||
return response?.Result?.FirstOrDefault();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Очереди
|
||||
/// <summary>Получает все очереди с разных устройств.</summary>
|
||||
public async Task<YQueueItemsContainer?> GetQueuesAsync(string? device = null)
|
||||
{
|
||||
var response = await _api.Queue.ListAsync(_storage, device);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Получает очередь по идентификатору.</summary>
|
||||
public async Task<YQueue?> GetQueueAsync(string queueId)
|
||||
{
|
||||
var response = await _api.Queue.GetAsync(_storage, queueId);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Создаёт новую очередь.</summary>
|
||||
public async Task<YNewQueue?> CreateQueueAsync(YQueue queue, string? device = null)
|
||||
{
|
||||
var response = await _api.Queue.CreateAsync(_storage, queue, device);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Обновляет позицию в очереди.</summary>
|
||||
public async Task<YUpdatedQueue?> UpdateQueuePositionAsync(string queueId, int currentIndex, bool isInteractive, string? device = null)
|
||||
{
|
||||
var response = await _api.Queue.UpdatePositionAsync(_storage, queueId, currentIndex, isInteractive, device);
|
||||
return response?.Result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Загрузка треков (UGC)
|
||||
/// <summary>Загружает трек из файла в плейлист.</summary>
|
||||
public async Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, string filePath)
|
||||
{
|
||||
var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName);
|
||||
var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, filePath);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Загружает трек из потока в плейлист.</summary>
|
||||
public async Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, Stream stream)
|
||||
{
|
||||
var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName);
|
||||
var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, stream);
|
||||
return response?.Result;
|
||||
}
|
||||
|
||||
/// <summary>Загружает трек из массива байтов в плейлист.</summary>
|
||||
public async Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, byte[] file)
|
||||
{
|
||||
var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName);
|
||||
var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, file);
|
||||
return response?.Result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Лейблы
|
||||
/// <summary>Получает альбомы лейбла с пагинацией.</summary>
|
||||
public async Task<List<YAlbum>> GetAlbumsByLabelAsync(YLabel label, int page = 0)
|
||||
{
|
||||
var response = await _api.Label.GetAlbumsByLabelAsync(_storage, label, page);
|
||||
return response?.Result?.Albums ?? new List<YAlbum>();
|
||||
}
|
||||
|
||||
/// <summary>Получает артистов лейбла с пагинацией.</summary>
|
||||
public async Task<List<YArtist>> GetArtistsByLabelAsync(YLabel label, int page = 0)
|
||||
{
|
||||
var response = await _api.Label.GetArtistsByLabelAsync(_storage, label, page);
|
||||
return response?.Result?.Artists ?? new List<YArtist>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Ynison (WebSocket плеер)
|
||||
/// <summary>Подключается к Ynison и запускает синхронизацию состояния плеера.</summary>
|
||||
public async Task ConnectYnisonAsync()
|
||||
{
|
||||
if (_player == null)
|
||||
_player = _api.Ynison.GetPlayer(_storage);
|
||||
await _player.ConnectAsync();
|
||||
}
|
||||
|
||||
/// <summary>Отключается от Ynison.</summary>
|
||||
public async Task DisconnectYnisonAsync()
|
||||
{
|
||||
if (_player != null)
|
||||
await _player.DisconnectAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>Освобождает ресурсы.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_player?.Dispose();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user