当前位置: 首页 > news >正文

微网站制作/杭州网络推广公司

微网站制作,杭州网络推广公司,昆明做网站的公司,大型网站开发播放控制服务 IMusicControlService: 播放控制类,用于当前平台播放器对象的操作,对当前所播放曲目的暂停/播放,下一首/上一首,快进快退(寻迹),随机、单曲模式等功能的控制。 播放控制类包含一…

播放控制服务

IMusicControlService:
播放控制类,用于当前平台播放器对象的操作,对当前所播放曲目的暂停/播放,下一首/上一首,快进快退(寻迹),随机、单曲模式等功能的控制。

播放控制类包含一个平台特定的播放器,由于要制作通用的播放控制类,IMusicControlService不开放播放器对象的公共访问,而是通过暴露方法操作播放器对象。

在跨平台中的实现:

  • Android平台使用Android.Media.MediaPlayer类
  • iOS平台使用AVFoundation.AVAudioPlayer类
  • Windows平台使用Windows.Media.Playback.MediaPlayer类

虽然不同平台的播放器类都提供了诸如播放,暂停,寻迹的功能,但不同平台存在着微小差别。
比如停止功能 - Stop:

在iOS中的实现:

public partial void Stop()
{if (!IsInitFinished()) { return; }if (CurrentIosPlayer.Playing){CurrentIosPlayer.Stop();OnPlayStatusChanged?.Invoke(this, false);}
}

在Android中,由于Android.Media.MediaPlayer没有提供Stop方法,所以停止的逻辑用寻迹至0位置暂停实现的

public partial void Stop()
{if (CurrentAndroidPlayer.IsPlaying){CurrentAndroidPlayer.SeekTo(0);CurrentAndroidPlayer.Pause();}
}

又如寻迹功能 - SeekTo
在iOS中的实现,postion参数为曲目开始后的时间值,单位秒。改变播放位置是通过直接赋值AVFoundation.AVAudioPlayer.CurrentTime实现的

public partial void SeekTo(double position){if (!IsInitFinished()) { return; }CurrentIosPlayer.CurrentTime = position;
}

在Android中,Android.Media.MediaPlayer提供了SeekTo方法,传入值是毫秒,因此要做一下转换:

public partial void SeekTo(double position)
{CurrentAndroidPlayer.SeekTo((int)position * 1000);}

在传统播放器随机播放时,如果下一曲不是我想听的,我仍然想听上一曲,由于上一曲按钮是随机触发的时机,你可能找不到它了,不得不再音乐列表再搜索它。这可能是个遗憾

我在这个随机模型中引入随机播放映射表,使得在随机模式中,上一曲/下一曲仍然能发挥其作用。

刷新随机列表:
increment为跳转步数,例如increment = 1时相当于下一曲,increment = -1 时相当于上一曲:

private partial int GetShuffleMusicIndex(int originItem, int increment)
{var originItemIndex = 0;foreach (var item in ShuffleMap){if (originItem == item){break;}originItemIndex++;}var newItemIndex = originItemIndex + increment;if (newItemIndex < 0){newItemIndex = LastIndex;}if (newItemIndex > LastIndex){newItemIndex = 0;}var shuffleMapCount = shuffleMap.Count();var musicInfosCount = MusicInfos.Count();if (shuffleMapCount != musicInfosCount){shuffleMap = CommonHelper.GetRandomArry(0, LastIndex);shuffleMapCount = shuffleMap.Count();}if (shuffleMapCount > 0 && newItemIndex < shuffleMapCount){var resultContent = ShuffleMap[newItemIndex];return resultContent;}else{return -1;}
}

GetRandomArry 方法将产生一个指定最小值到最大值连续数列的随机数组

public static int[] GetRandomArry(int minval, int maxval)
{int[] arr = new int[maxval - minval + 1];int i;//初始化数组for (i = 0; i <= maxval - minval; i++){arr[i] = i + minval;}//随机数Random r = new Random();for (int j = maxval - minval; j >= 1; j--){int address = r.Next(0, j);int tmp = arr[address];arr[address] = arr[j];arr[j] = tmp;}//输出foreach (int k in arr){Debug.WriteLine(k + " ");}return arr;
}

关键属性:

  • ShuffleMap - 随机播放映射表
  • MusicInfos - 播放器音频列表
  • LastIndex - 当前播放曲目位于器音频列表位置角标

关键方法:

  • Play - 播放
  • PauseOrResume - 暂停/恢复
  • RebuildMusicInfos - 从播放列队中读取音频列表,刷新播放器队列
  • SeekTo - 快进快退(寻迹)
  • GetNextMusic - 获取下一首曲目信息
  • GetPreMusic - 获取上一首曲目信息
  • InitPlayer - 初始化播放器
  • UpdateShuffleMap - 更新随机播放映射表
  • SetRepeatOneStatus - 设置是否单曲循环
  • Duration - 获取当前曲目时长
  • CurrentTime - 获取当前曲目播放进度
  • IsPlaying - 获取是否在播放中
  • IsInitFinished - 获取是否完成播放器初始化

关键事件:

  • OnPlayFinished - 完成当前曲目播放时触发
  • OnRebuildMusicInfosFinished - 完成刷新播放器队列触发
  • OnProgressChanged - 播放进度更改时触发
  • OnPlayStatusChanged - 播放状态变更时触发

接口定义:

public interface IMusicControlService
{event EventHandler<bool> OnPlayFinished;event EventHandler OnRebuildMusicInfosFinished;event EventHandler<double> OnProgressChanged;event EventHandler<bool> OnPlayStatusChanged;public IMusicInfoManager MusicInfoManager { get; set; }int[] ShuffleMap { get; }List<MusicInfo> MusicInfos { get; }int LastIndex { get; }Task RebuildMusicInfos(Action callback);void SeekTo(double position);MusicInfo GetNextMusic(MusicInfo current, bool isShuffle);MusicInfo GetPreMusic(MusicInfo current, bool isShuffle);int GetMusicIndex(MusicInfo musicInfo);MusicInfo GetMusicByIndex(int index);Task InitPlayer(MusicInfo musicInfo);void Play(MusicInfo currentMusic);void Stop();void PauseOrResume();void PauseOrResume(bool status);Task UpdateShuffleMap();void SetRepeatOneStatus(bool isRepeatOne);double Duration();double CurrentTime();bool IsPlaying();bool IsInitFinished();
}

曲目管理器设计

IMusicInfoManager:
曲目管理类,用于歌曲队列,歌单的编辑;各曲目集合增加,删除等功能

歌曲队列,歌单等信息存在于本地数据库,曲目管理类将对这些数据增、删、查、改的操作,Abp框架实现的仓储模式为我们生成了Repository对象。

MusicInfoManager构造函数中注入各仓储依赖

public MusicInfoManager(IRepository<Queue, long> queueRepository,IRepository<PlaylistItem, long> playlistItemRepository,IRepository<Playlist, long> playlistRepository,IUnitOfWorkManager unitOfWorkManager)
{...
}

读取播放队列

播放队列具有一定的代表性,歌单的逻辑与播放队列类似,所以本篇博文着重讲述播放队列的业务

播放队列存在于本地数据库的Queue表中,全部将他们读取。

播放队列的Entry项和设备中的媒体条目是一种弱关联,需要将他们“螯合”起来,连表左联查询后取得MusicInfo集合。

[UnitOfWork]
public async Task<List<MusicInfo>> GetQueueEntry()
{var queueEntrys = await queueRepository.GetAll().ToListAsync();if (_musicInfos == null || _musicInfos.Count == 0){var isSucc = await GetMusicInfos();if (!isSucc.IsSucess){//CommonHelper.ShowNoAuthorized();}_musicInfos = isSucc.Result;}var result =from queue in queueEntrysjoin musicInfo in _musicInfoson queue.MusicInfoId equals musicInfo.Idorderby queue.Rankselect musicInfo;return result.ToList();
}

返回时依据Rank字段递增排序。

添加播放队列

播放整个专辑时,将整个专辑中的所有曲目添加到播放队列:

QueueAllAction在点击播放专辑时触发,首先清空当前播放队列,接着将当前页面绑定的曲目集合(Musics对象)插入到播放队列

private async void QueueAllAction(object obj)
{await MusicInfoManager.ClearQueue();var result = await MusicInfoManager.CreateQueueEntrys(Musics);..
}

MusicInfoManager.cs 中定义了清空播放队列ClearQueue,和歌单中创建曲目集合方法CreateQueueEntrys:

[UnitOfWork]
public async Task ClearQueue()
{await queueRepository.DeleteAsync(c => true);}
[UnitOfWork]
public async Task<bool> CreateQueueEntrys(List<MusicInfo> musicInfos)
{var lastItemRank = queueRepository.GetAll().OrderBy(c => c.Rank).Select(c => c.Rank).LastOrDefault();var entrys = new List<Queue>();foreach (var music in musicInfos){var entry = new Queue(music.Title, lastItemRank, music.Id);lastItemRank++;entrys.Add(entry);}await queueRepository.GetDbContext().AddRangeAsync(entrys);return true;
}

需要注意的是,Rank字段将在队列最后一条后继续递增

曲目排序

曲目排序,原理是通过交换位置实现的,iOS和Android平台都有自己的可排序列表控件,在对选中的条目进行排序(往往是提起条目-拖拽-释放)的过程中,触发事件往往提供当前条目oldMusicInfo,和排斥条目newMusicInfo,调用ReorderQueue时将这辆个参数传入,将这两个MusicInfo的Rank值交换:

[UnitOfWork]
public void ReorderQueue(MusicInfo oldMusicInfo, MusicInfo newMusicInfo)
{var oldMusic = queueRepository.FirstOrDefault(c => c.MusicTitle==oldMusicInfo.Title);var newMusic = queueRepository.FirstOrDefault(c => c.MusicTitle==newMusicInfo.Title);if (oldMusic ==null || newMusic==null){return;}var oldRank = oldMusic.Rank;oldMusic.Rank=newMusic.Rank;newMusic.Rank=oldRank;queueRepository.Update(oldMusic);queueRepository.Update(newMusic);
}

下一首播放

下一首播放将播放队列中,指定的曲目排在当前播放曲目之后,实现方式是线确保目标曲目存在于播放队列。同样,用到了排序逻辑,再将他的排序(Rank值)与当前播放曲目之后的做交换。

public partial async Task<bool> InsertToEndQueueEntry(MusicInfo musicInfo)
{var result = false;var isSuccessCreate = false;//如果没有则先创建if (!await GetIsQueueContains(musicInfo.Title)){isSuccessCreate = await CreateQueueEntry(musicInfo);await unitOfWorkManager.Current.SaveChangesAsync();}else{isSuccessCreate = true;}//确定包含后与下一曲交换位置if (isSuccessCreate){var current = currentMusic;Queue newMusic = null;var lastItem = await queueRepository.FirstOrDefaultAsync(c => c.MusicTitle==current.Title);if (lastItem!=null){newMusic = await queueRepository.FirstOrDefaultAsync(c => c.Rank==lastItem.Rank+1);}var oldMusic = await queueRepository.FirstOrDefaultAsync(c => c.MusicTitle==musicInfo.Title);if (oldMusic ==null || newMusic==null){return true;}var oldRank = oldMusic.Rank;oldMusic.Rank=newMusic.Rank;newMusic.Rank=oldRank;queueRepository.Update(oldMusic);queueRepository.Update(newMusic);result = true;}else{result = false;}return result;
}

其它关键方法:

  • ClearQueue - 从播放队列中清除所有曲目
  • CreatePlaylist - 创建歌单
  • CreatePlaylistEntry - 歌单中创建曲目
  • CreatePlaylistEntrys - 歌单中创建曲目集合
  • CreatePlaylistEntrysToMyFavourite - “我最喜爱”中插入曲目集合
  • CreateQueueEntry - 播放队列中创建曲目
  • CreateQueueEntrys - 播放队列中创建曲目集合
  • DeleteMusicInfoFormQueueEntry - 从队列中删除指定曲目
  • DeletePlaylist - 删除歌单
  • DeletePlaylistEntry - 从歌单中删除曲目
  • DeletePlaylistEntryFromMyFavourite - 从“我最喜爱”中删除曲目
  • GetMusicInfos - 获取曲目集合
  • GetAlbumInfos - 获取专辑集合
  • GetArtistInfos - 获取艺术家集合
  • GetAlphaGroupedMusicInfo - 获取分组包装好的曲目集合
  • GetAlphaGroupedAlbumInfo - 获取分组包装好的专辑集合
  • GetAlphaGroupedArtistInfo - 获取分组包装好的艺术家集合
  • GetIsMyFavouriteContains - 曲目是否包含在"我最喜爱"中
  • GetIsPlaylistContains - 曲目是否包含在歌单中
  • GetIsQueueContains - 曲目是否包含在播放队列中
  • GetPlaylist - 获取歌单列表
  • GetPlaylistEntry - 获取歌单列表
  • GetPlaylistInfo - 获取歌单中的曲目
  • GetQueueEntry - 获取播放队列中的曲目
  • InsertToEndQueueEntry - 插入曲目到播放队列中的末尾
  • InsertToEndQueueEntrys - 插入曲目集合到播放队列中的末尾
  • InsertToNextQueueEntry - 插入曲目到队列中的下一曲(下一首播放)
  • UpdatePlaylist - 更新歌单信息

接口定义:

public interface IMusicInfoManager
{Task ClearQueue();Task<bool> CreatePlaylist(Playlist playlist);Task<bool> CreatePlaylistEntry(MusicInfo musicInfo, long playlistId);Task<bool> CreatePlaylistEntrys(List<MusicInfo> musics, long playlistId);Task<bool> CreatePlaylistEntrys(MusicCollectionInfo musicCollectionInfo, long playlistId);Task<bool> CreatePlaylistEntrysToMyFavourite(List<MusicInfo> musics);Task<bool> CreatePlaylistEntrysToMyFavourite(MusicCollectionInfo musicCollectionInfo);Task<bool> CreatePlaylistEntryToMyFavourite(MusicInfo musicInfo);Task<bool> CreateQueueEntry(MusicInfo musicInfo);Task<bool> CreateQueueEntrys(List<MusicInfo> musicInfos);Task<bool> CreateQueueEntrys(MusicCollectionInfo musics);Task<bool> DeleteMusicInfoFormQueueEntry(MusicInfo musicInfo);Task<bool> DeleteMusicInfoFormQueueEntry(string musicTitle);Task<bool> DeletePlaylist(long playlistId);Task<bool> DeletePlaylist(Playlist playlist);Task<bool> DeletePlaylistEntry(MusicInfo musicInfo, long playlistId);Task<bool> DeletePlaylistEntry(string musicTitle, long playlistId);Task<bool> DeletePlaylistEntryFromMyFavourite(MusicInfo musicInfo);Task<InfoResult<List<AlbumInfo>>> GetAlbumInfos();Task<AlphaGroupedObservableCollection<AlbumInfo>> GetAlphaGroupedAlbumInfo();Task<AlphaGroupedObservableCollection<ArtistInfo>> GetAlphaGroupedArtistInfo();Task<AlphaGroupedObservableCollection<MusicInfo>> GetAlphaGroupedMusicInfo();Task<InfoResult<List<ArtistInfo>>> GetArtistInfos();Task<bool> GetIsMyFavouriteContains(MusicInfo musicInfo);Task<bool> GetIsMyFavouriteContains(string musicTitle);Task<bool> GetIsPlaylistContains(MusicInfo musicInfo, long playlistId);Task<bool> GetIsPlaylistContains(string musicTitle, long playlistId);Task<bool> GetIsQueueContains(string musicTitle);Task<InfoResult<List<MusicInfo>>> GetMusicInfos();Task<List<Playlist>> GetPlaylist();Task<List<MusicInfo>> GetPlaylistEntry(long playlistId);Task<List<MusicInfo>> GetPlaylistEntryFormMyFavourite();Task<List<PlaylistInfo>> GetPlaylistInfo();Task<List<MusicInfo>> GetQueueEntry();Task<bool> InsertToEndQueueEntry(MusicInfo musicInfo);Task<bool> InsertToEndQueueEntrys(List<MusicInfo> musicInfos);Task<bool> InsertToNextQueueEntry(MusicInfo musicInfo, MusicInfo currentMusic);Task<bool> UpdatePlaylist(Playlist playlist);
}

获取本地音乐

Android中的实现

在Android平台中MatoMusic.Core\Platforms\Android\MusicInfoManager.cs

MediaStore类是Android平台的多媒体数据库,它包含了音频,视频,图片等所有多媒体文件信息。

Android扫描服务会在后台自动扫描设备文件资源,将设备上的音乐媒体信息加入到MediaStore数据库中。应用程序通过Android平台提供的ContentProvider包含的API直接从MediaStore中读取相应的媒体信息。

获取设备多媒体信息的实现方式如下:

public IList<MusicInfo> GetAllSongs()
{IList<MusicInfo> songs = new ObservableCollection<MusicInfo>();ICursor mediaCursor, genreCursor, albumCursor;mediaCursor = Application.Context.ContentResolver.Query(MediaStore.Audio.Media.ExternalContentUri,_mediaProjections, null, null,MediaStore.Audio.Media.InterfaceConsts.TitleKey);int artistColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.Artist);int albumColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.Album);int titleColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.Title);int durationColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.Duration);int uriColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.Data);int idColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.Id);int isMusicColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.IsMusic);int albumIdColumn = mediaCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.AlbumId);int isMusic;ulong duration, id;string artist, album, title, uri, genre, artwork, artworkId;if (mediaCursor.MoveToFirst()){do{isMusic = int.Parse(mediaCursor.GetString(isMusicColumn));if (isMusic != 0){ulong.TryParse(mediaCursor.GetString(durationColumn),out duration);artist = mediaCursor.GetString(artistColumn);album = mediaCursor.GetString(albumColumn);title = mediaCursor.GetString(titleColumn);uri = mediaCursor.GetString(uriColumn);ulong.TryParse(mediaCursor.GetString(idColumn), out id);artworkId = mediaCursor.GetString(albumIdColumn);genreCursor = Application.Context.ContentResolver.Query(MediaStore.Audio.Genres.GetContentUriForAudioId("external", (int)id),_genresProjections, null, null, null);int genreColumn = genreCursor.GetColumnIndex(MediaStore.Audio.Genres.InterfaceConsts.Name);if (genreCursor.MoveToFirst()){genre = genreCursor.GetString(genreColumn) ?? string.Empty;}else{genre = string.Empty;}//https://stackoverflow.com/questions/63181820/why-is-album-art-the-only-field-that-returns-null-from-mediastore-when-others-arImageSource artworkImage = null;if (DeviceInfo.Version.Major < 10){albumCursor = Application.Context.ContentResolver.Query(MediaStore.Audio.Albums.ExternalContentUri,_albumProjections,$"{MediaStore.Audio.Albums.InterfaceConsts.Id}=?",new string[] { artworkId },null);int artworkColumn = albumCursor.GetColumnIndex(MediaStore.Audio.Media.InterfaceConsts.AlbumArt);if (albumCursor.MoveToFirst()){artwork = albumCursor.GetString(artworkColumn) ?? string.Empty;}else{artwork = String.Empty;}albumCursor?.Close();artworkImage = artwork;}else{var extUrl = MediaStore.Audio.Albums.ExternalContentUri;var albumArtUri = ContentUris.WithAppendedId(extUrl, long.Parse(artworkId));try{//var art = System.IO.Path.Combine (Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "albumart" + artworkId + ".jpg");var art = System.IO.Path.Combine(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments).AbsolutePath, "albumart" + artworkId + ".jpg");var bitmap = Application.Context.ContentResolver.LoadThumbnail(albumArtUri, new Android.Util.Size(1024, 1024), null);var h = bitmap.Height;var w = bitmap.Width;var bb = bitmap.ByteCount;using (Stream ms = new FileStream(art, FileMode.Create)){bitmap.Compress(Bitmap.CompressFormat.Png, 100, ms);bitmap.Recycle();}artworkImage = art;}catch (Exception e){System.Console.WriteLine(e.Message);}}songs.Add(new MusicInfo(){Id = (int)id,Title = title,Artist = artist,AlbumTitle = album,Genre = genre,Duration = duration / 1000,Url = uri,AlbumArt = artworkImage});genreCursor?.Close();}} while (mediaCursor.MoveToNext());}mediaCursor?.Close();return songs;}

获取音乐信息集合

public partial async Task<InfoResult<List<MusicInfo>>> GetMusicInfos()
{List<MusicInfo> musicInfos;var result = false;if (await MediaLibraryAuthorization()){musicInfos = await Task.Run(() =>{var Infos = (from item in GetAllSongs()select new MusicInfo(){Id = item.Id,Title = item.Title,Duration = item.Duration,Url = item.Url,AlbumTitle = item.AlbumTitle,Artist = item.Artist,AlbumArt = item.AlbumArt,GroupHeader = GetGroupHeader(item.Title),IsFavourite = GetIsMyFavouriteContains(item.Title).Result,IsInitFinished = true}).ToList();return Infos;});result = true;}else{musicInfos = new List<MusicInfo>();result = false;}return new InfoResult<List<MusicInfo>>(result, musicInfos);}

iOS中的实现

在iOS平台中MatoMusic.Core\Platforms\iOS\MusicInfoManager.cs

在iOS平台中获取音乐信息要简单得多,MPMediaQuery这个类获取通系统自带的‘音乐’软件下载的,或通过iTunes导入的本地歌曲文件

MPMediaQuery 类使用方式可以参考官方文档

获取音乐信息集合

public partial async Task<InfoResult<List<MusicInfo>>> GetMusicInfos()
{List<MusicInfo> musicInfos;var result = false;if (await MediaLibraryAuthorization()){musicInfos = await Task.Run(() =>{var Infos = (from item in MediaQuery.Itemswhere item.MediaType == MPMediaType.Musicselect new MusicInfo(){Id = (int)item.PersistentID,Title = item.Title,Url = item.AssetURL.ToString(),Duration = Convert.ToUInt64(item.PlaybackDuration),AlbumTitle = item.AlbumTitle,Artist = item.Artist,AlbumArt = GetAlbumArtSource(item),GroupHeader = GetGroupHeader(item.Title),IsFavourite = GetIsMyFavouriteContains(item.Title).Result,IsInitFinished = true}).ToList();return Infos;});result = true;}else{musicInfos = new List<MusicInfo>();result = false;}return new InfoResult<List<MusicInfo>>(result, musicInfos);}

Windows中的实现

在Windows设备中,需要指定一个主目录来扫描音乐文件,我们指定一个缺省目录,如“音乐”文件夹(KnownFolders.MusicLibrary),好跟之前两个平台的行为保持一致

private async Task<List<MusicInfo>> SetMusicListAsync(StorageFolder musicFolder = null)
{var localSongs = new List<MusicInfo>();List<StorageFile> songfiles = new List<StorageFile>();if (musicFolder == null){musicFolder = KnownFolders.MusicLibrary;}await GetLocalSongsAysnc(songfiles, musicFolder);localSongs = await PopulateSongListAsync(songfiles);return localSongs;}

递归调用GetLocalSongsAysnc,遍历主目录以及其子目录的所有.mp3文件

private async Task GetLocalSongsAysnc(List<StorageFile> songFiles, StorageFolder parent)
{foreach (var item in await parent.GetFilesAsync()){if (item.FileType == ".mp3")songFiles.Add(item);}foreach (var folder in await parent.GetFoldersAsync()){await GetLocalSongsAysnc(songFiles, folder);}
}

从本地文件读取音频信息,转成曲目信息

private async Task<List<MusicInfo>> PopulateSongListAsync(List<StorageFile> songFiles)
{var localSongs = new List<MusicInfo>();int Id = 1;foreach (var file in songFiles){MusicInfo song = new MusicInfo();// 1. 获取文件信息MusicProperties musicProperty = await file.Properties.GetMusicPropertiesAsync();if (!string.IsNullOrEmpty(musicProperty.Title))song.Title = musicProperty.Title;else{song.Title = file.DisplayName;}StorageItemThumbnail currentThumb = await file.GetThumbnailAsync(ThumbnailMode.MusicView, 60, ThumbnailOptions.UseCurrentScale);// 2.将文件信息转换为数据模型string coverUri = "ms-appx:///Assets/Default/Default.jpg";song.Id = Id;song.Url = file.Path;song.GroupHeader = GetGroupHeader(song.Title);if (!string.IsNullOrEmpty(musicProperty.Artist))song.Artist = musicProperty.Artist;elsesong.Artist = "未知歌手";if (!string.IsNullOrEmpty(musicProperty.Album))song.AlbumTitle = musicProperty.Album;elsesong.AlbumTitle = "未知唱片";song.Duration = (ulong)musicProperty.Duration.TotalSeconds;//3. 添加至UI集合中var task01 = SaveImagesAsync(file, song);var result = await task01;var task02 = task01.ContinueWith((e) =>{if (result.IsSucess){song.AlbumArtPath = result.Result;}else{song.AlbumArtPath = coverUri;}});Task.WaitAll(task01, task02);song.IsInitFinished = true;localSongs.Add(song);Id++;}return localSongs;
}

获取音乐信息集合

public partial async Task<InfoResult<List<MusicInfo>>> GetMusicInfos()
{List<MusicInfo> musicInfos;var result = false;if (await MediaLibraryAuthorization()){musicInfos = await SetMusicListAsync();result = true;}else{musicInfos = new List<MusicInfo>();result = false;}return new InfoResult<List<MusicInfo>>(result, musicInfos);}

获取专辑和艺术家

专辑信息包含了音乐集合

获取专辑和艺术家的跨平台的实现方式大同小异,以Android平台为例

GetAlbumInfos方法用于获取AlbumInfo集合

public partial async Task<InfoResult<List<AlbumInfo>>> GetAlbumInfos()
{List<AlbumInfo> albumInfo;var result = false;if (await MediaLibraryAuthorization()){var isSucc = await GetMusicInfos();if (!isSucc.IsSucess){//CommonHelper.ShowNoAuthorized();}albumInfo = await Task.Run(() =>{var info = (from item in isSucc.Resultgroup item by item.AlbumTitleinto cselect new AlbumInfo(){Title = c.Key,GroupHeader = GetGroupHeader(c.Key),AlbumArt = c.FirstOrDefault().AlbumArt,Musics = new ObservableCollection<MusicInfo>(c.Select(d => new MusicInfo(){Id = d.Id,Title = d.Title,Duration = d.Duration,Url = d.Url,AlbumTitle = d.AlbumTitle,Artist = d.Artist,AlbumArt = d.AlbumArt,IsFavourite = GetIsMyFavouriteContains(d.Title).Result,IsInitFinished = true}))}).ToList();return info;});result = true;}else{albumInfo = new List<AlbumInfo>();result = false;}return new InfoResult<List<AlbumInfo>>(result, albumInfo);}

GetArtistInfos方法用于获取ArtistInfo集合

public partial async Task<InfoResult<List<ArtistInfo>>> GetArtistInfos()
{List<ArtistInfo> artistInfo;var result = false;if (await MediaLibraryAuthorization()){var isSucc = await GetMusicInfos();if (!isSucc.IsSucess){//CommonHelper.ShowNoAuthorized();}artistInfo = await Task.Run(() =>{var info = (from item in isSucc.Resultgroup item by item.Artistinto cselect new ArtistInfo(){Title = c.Key,GroupHeader = GetGroupHeader(c.Key),Musics = new ObservableCollection<MusicInfo>(c.Select(d => new MusicInfo(){Id = d.Id,Title = d.Title,Duration = d.Duration,Url = d.Url,AlbumTitle = d.AlbumTitle,Artist = d.Artist,AlbumArt = d.AlbumArt,IsFavourite = GetIsMyFavouriteContains(d.Title).Result,IsInitFinished = true}))}).ToList();return info;});result = true;}else{artistInfo = new List<ArtistInfo>();result = false;}return new InfoResult<List<ArtistInfo>>(result, artistInfo);
}

项目地址

GitHub:MatoMusic

相关文章:

深入解读.NET MAUI音乐播放器项目(二):播放内核

播放控制服务 IMusicControlService: 播放控制类&#xff0c;用于当前平台播放器对象的操作&#xff0c;对当前所播放曲目的暂停/播放&#xff0c;下一首/上一首&#xff0c;快进快退&#xff08;寻迹&#xff09;&#xff0c;随机、单曲模式等功能的控制。 播放控制类包含一…...

4.SpringWeb

一、创建项目LomBok:辅助开发工具&#xff0c;减少代码编写Spring Web:带上Spring MVC,可以做Web开发了Thymleaf: Web开发末班引擎&#xff08;不常用&#xff09;创建好&#xff0c;如下&#xff1a;static/ 放置静态资源的根目录templates/ 放置模板文件的根目录 二、资源配置…...

C++中的枚举与位域

枚举在传统 C中&#xff0c;枚举类型并非类型安全&#xff0c;枚举类型会被视作整数&#xff0c;则会让两种完全不同的枚举类型可以进行直接的比较&#xff08;虽然编译器给出了检查&#xff0c;但并非所有&#xff09;&#xff0c;甚至同一个命名空间中的不同枚举类型的枚举值…...

第19章 MongoDB Limit与Skip方法教程

第19章 MongoDB Limit与Skip方法教程 MongoDB Limit() 方法 如果仁兄需要在MongoDB中读取指定数量的数据记录&#xff0c;可以使用MongoDB的Limit方法&#xff0c;limit()方法接受一个数字参数&#xff0c;该参数指定从MongoDB中读取的记录条数。 语法 limit()方法基本语法请…...

进程间通信——消息队列

多线程 进程间通信——消息队列 消息队列——发送 测试代码 #include <sys/types.h> #include <sys/msg.h> #include <sys/ipc.h>#include <stdlib.h> #include <stdio.h> #include <string.h>#define MAX_BUF_SIZE 255struct msgtype {…...

OpenMMLab 实战营打卡 - 第 7 课

OpenMMLab MMSegmentation内容概要MMSegmentation统一超参MMSegmentation 的项目结构分割模型的模块化设计分割模型的配置文件主干网络的配置ResNet v1c主解码头的配置辅助解码头的配置数据集配置数据处理流水线常用训练策略参考资料内容概要 • MMSegmentation 项目概述 • M…...

MAC Boook打印长图

有时老师给留的作业是一张长图&#xff0c;直接打印或者通过把图放入word打印都不能实现把长页分成多页进行打印。通过网上找到思路可以通过EXCEL实现将长图分成多页打印。 测试版本 macos&#xff1a;ventura 13.1 office 365 注&#xff1a;同样适用windows版本的excel 第…...

web3:区块链共识机制系列-POS(Proof of Stake)股权证明算法

web3相关学习一并收录至该博客&#xff1a;web3学习博客目录大全 前情衔接&#xff1a;web3:区块链常见的几大共识机制及优缺点 目录前言算法公式与原理算法公式运作原理以Peer Coin为例缺陷优点缺点特点分类发展历程casper协议1.什么是无成本利益关系问题2.引入casper协议解决…...

Linux fork()系统调用流程解析

1. fork()函数介绍&#xff08;百度百科&#xff09; fork系统调用用于创建一个新进程&#xff0c;称为子进程&#xff0c;它与进程&#xff08;称为系统调用fork的进程&#xff09;同时运行&#xff0c;此进程称为父进程。创建新的子进程后&#xff0c;两个进程将执行fork&…...

自定义软件帮助文档(qt assistant实现)

网上搜了一下&#xff0c;软件的帮助文档&#xff0c;三个都可以&#xff1a;https://github.com/zealdocs/zeal&#xff0c;https://zealdocs.org/&#xff0c;看看这个博客说的 https://blog.csdn.net/libaineu2004/article/details/125028913&#xff0c;这个也是开源的&…...

ESP32设备驱动-GPIO外部中断

GPIO外部中断 文章目录 GPIO外部中断1、GPIO中断介绍2、GPIO中断使用步骤3、软件准备4、硬件准备5、代码实现在前面的文章 ESP32设备驱动-GPIO数字输入与输出中介绍如何对GPIO进行控制操作。本文将在该基础上使用GPIO中断进一步优化按键输入。即演示如何使用GPIO中断。 1、GPI…...

【安全】nginx反向代理+负载均衡上传webshel

Nginx负载均衡下上传webshell 什么是反向代理&#xff1f; 正向代理就是代替客户端进行各种服务的访问以及获取&#xff1b;那么反向代理自然就是代替服务器进行事务处理&#xff0c;就是此时的代理服务器负责将用户的各项请求做一个汇总、分类&#xff0c;将其分发到不同的服务…...

华为OD机试 - 单词接龙(Python)| 真题,思路,知识点

单词接龙 题目 单词接龙的规则是: 可用于接龙的单词,首字母必须要与前一个单词的尾字母相同; 当存在多个首字母相同的单词时,取长度最长的单词; 如果长度也相等,则取字典序最小的单词; 已经参与接龙的单词不能重复使用; 现给定一组全部由小写字母组成的单词数组, 并指…...

[ 系统安全篇 ] window 命令禁用用户及解禁方法

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…...

Https 协议超强讲解(二)

浏览器是如何确保 CA 证书的合法性&#xff1f; 1. 证书包含什么信息&#xff1f; 颁发机构信息 公钥 公司信息 域名 有效期 指纹 …… 2. 证书的合法性依据是什么&#xff1f; 首先&#xff0c;权威机构是要有认证的&#xff0c;不是随便一个机构都有资格颁发证书&am…...

C语言的程序环境和预处理详解

目录 一、程序的翻译环境和执行环境 二、编译和链接详解 2、1 翻译环境 2、2 编译过程详解 2、3 执行环境 三、预处理详解 3、1 预定义符号 3、2 #define 3、2、1 #define定义的符号 3、2、2 #define 定义宏 3、2、3 #define 替换规则 3、3 宏和函数的对比 3、4 条件编译 3、5…...

3.JUC【Java面试第三季】

3.JUC【Java面试第三季】前言推荐3.JUC06_闲聊AQS面试1.题目说明07_可重入锁理论2.可重入锁说明“可重入锁”这四个字分开来解释可重入锁的种类08_可重入锁的代码验证-上09_可重入锁的代码验证-下3.LockSupport10_LockSupport是什么LockSupport是什么11_waitNotify限制线程等待…...

Linux防火墙(7)

实验目的 通过该实验了解Linux防火墙iptables实现原理&#xff0c;掌握iptables基本使用方法&#xff0c;能够利用iptables对操作系统进行加固。预备知识基本原理 netfilter/iptables&#xff08;简称为iptables&#xff09;组成Linux平台下的包过滤防火墙&#xff0c;具有完成…...

2.11整理(2)(主要关于teacher forcing)

teacher forcing 训练迭代过程早期的RNN预测能力非常弱&#xff0c;几乎不能给出好的生成结果。如果某一个unit产生了垃圾结果&#xff0c;必然会影响后面一片unit的学习。RNN存在着两种训练模式(mode): free-running mode&#xff1a;就是常见的那种训练网络的方式: 上一个sta…...

亿级高并发电商项目-- 实战篇 --万达商城项目 三(通用模块、商品服务模块、后台API模块、IDEA忽略文件显示等开发工作

专栏&#xff1a;高并发项目 &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框…...

IDEA下java程序的调试(简易实例图示版)

在线排版不太好看&#xff0c;介意的读者可下载word下来看&#xff1a;https://download.csdn.net/download/xijinno1/87441301IDEA下java程序的简单调试-System.out.println首先本次进行调试的一个程序是实现从1累加到100的功能&#xff0c;是在IDEA下进行编写的。如图所示&am…...

动态规划算法

1.应用场景-背包问题 背包问题&#xff1a;有一个背包&#xff0c;容量为 4 磅 &#xff0c; 现有如下物品 要求达到的目标为装入的背包的总价值最大&#xff0c;并且重量不超出要求装入的物品不能重复 2.动态规划算法介绍 动态规划(Dynamic Programming)算法的核心思想是&…...

nacos的单机模式和集群模式

文章目录 目录 文章目录 前言 一、nacos数据库配置 二、单机模式 三、集群模式 四、使用nginx集群模式的负载均衡 总结 前言 一、nacos数据库配置 在数据库中创建nacos_config 编码格式utf8-mb4的数据库 把上面的数据库文件导入数据库 在 配置文件中添加如下 spring.datasour…...

Spring Boot 整合定时任务完成 从0 到1

Java 定时任务学习 定时任务概述 > 定时任务的应用场景非常广泛, 如果说 我们想要在某时某地去尝试的做某件事 就需要用到定时任务来通知我们 &#xff0c;大家可以看下面例子 如果需要明天 早起&#xff0c;哪我们一般会去定一个闹钟去通知我们, 而在编程中 有许许多多的…...

Dialogue Transformers

Abstract 本文介绍了一种基于 Transformer 架构的 对话策略,其中自注意力机制被应用于对话轮次(dialogue turns)的序列上。近期的一些工作使用层次化的循环神经网络(hierarchical recurrent neural networks)在对话上下文中对多个话语(utterances)进行编码,但是我们认…...

【遇见青山】项目难点:缓存击穿问题解决方案

【遇见青山】项目难点&#xff1a;缓存击穿问题解决方案1.缓存击穿互斥锁&#x1f512;方案逻辑过期方案2.基于互斥锁方案的具体实现3.基于逻辑过期方案的具体实现1.缓存击穿 缓存击穿问题也叫热点Key问题&#xff0c;就是一个被高并发访问并且缓存重建业务较复杂的key突然失效…...

2023Flag具体实施计划(短期)

重新看了flag ,要做的事情太多&#xff0c;太杂&#xff0c;上周一周时间都在纠结和琢磨&#xff0c;该怎么下手。如何达成小目标。特别是沟通&#xff0c;汇报&#xff0c;演讲能力&#xff0c; 以及整体体系化的思维能力的训练。如何做到多思考&#xff0c;而不是瞎搞。这边重…...

研一寒假C++复习笔记--左值和右值的理解和使用

目录 1--左值和右值的定义 2--简单理解左值和右值的代码 3--非const引用只能接受左值 1--左值和右值的定义 左值&#xff1a;L-Value&#xff0c;L理解为 Location&#xff0c;表示可寻&#xff1b; 右值&#xff1a;R-Value&#xff0c;R理解为 Read&#xff0c;表示可读&a…...

Android 11.0 动态修改SystemProperties中ro开头系统属性的值

需求&#xff1a; 在11.0的产品开发中&#xff0c;对于定制功能的需求很多&#xff0c;有些机型要求可以修改系统属性值&#xff0c;对于系统本身在10.0以后为了系统安全性&#xff0c;不允许修改ro开头的SystemProperties的值&#xff0c;所以如果要求修改ro的相关系统属性&am…...

为什么分库分表

系列文章目录 文章目录系列文章目录前言一、什么是分库分表二、分库分表的原因分库分表三、如何分库分表3.1 垂直拆分1.垂直分库2、垂直分表3.2 水平拆分水平分库水平分表水平分库分表的策略hash取模算法range范围rangehash取模混合地理位置分片预定义算法四、分库分表的问题分…...