肥宅钓鱼网
当前位置: 首页 钓鱼百科

有啥听歌的好软件(写一个想听啥听啥的听歌软件)

时间:2023-06-11 作者: 小编 阅读量: 1 栏目名: 钓鱼百科

key=$searchText';varresponse=awaitDio().get;returnMiguPageListModel;}//获取播放地址staticFuture<String>getPlayUrlasync{varcopyrightId=song.originMap['copyrightId'];varpath='http://iecoxe.top:5000/v1/migu/song?

前言

前面我发了一篇文章,向大家推荐了一个听歌软件:Listen(原文链接),我一般是在Edge浏览器里安装插件使用的,但是由于我是用的iPhone手机,然后Listen的源码我又一直编译不过去(吐槽下ReactNative)所以想自己撸一个听歌软件自己用.

技术选型

本人原本是做ios开发的,但是奈何objective-C比较难用,swift又一言难尽,swiftUI坑又比较多.在加上最近flutter比较火,而我之前也在demo上试了写了一下,感觉还不错,所以就打算使用flutter来写app了.

而且flutter还是跨平台的,还可以把android windows的一起给搞定了,嘿嘿~ 美滋滋

api接口

巧妇难为无米之炊,没有接口也没法进行下去啊. 我第一时间想到的是抓Listen1的包. 但是咪咕渠道的有个sid字段的值不知道是什么规则生成的,每次都不一样.导致我咪咕渠道的api迟迟搞不定.

不过我在github上找到了其它可用的api(链接地址),谢谢这位大哥了嘿嘿.

开始撸代码先撸一个公共类,作为解析各平台的解析协议

class SongModel {Map<String, dynamic> originMap = {}; //原始数据String channel = ""; //渠道//资源idString resourceId() {return "";}//歌曲名String songName() {return "";}//歌曲图片Future<String> songImg() {return Future.value("");}//歌手名String singer() {return '';}//专辑名String album() {return "";}//播放地址Future<String> playUrl() async {return "";}//获取歌词Future<String> lyric() async {return "";}}

特定平台的api实现

import 'package:dio/dio.dart';import 'package:listen_flutter/api/migu/model/models/migu_models.dart';class MiguApi {//搜索歌曲static Future<MiguPageListModel> search(String? text) async {var searchText = "jay";if (text?.isNotEmpty == true) {searchText = text!;}var path = 'http://iecoxe.top:5000/v1/migu/search?key=$searchText';var response = await Dio().get(path);return MiguPageListModel(response.data);}//获取播放地址static Future<String> getPlayUrl(MiguSongModel song) async {var copyrightId = song.originMap['copyrightId'];var path = 'http://iecoxe.top:5000/v1/migu/song?cid=$copyrightId';var response = await Dio().get(path);var map = response.data as Map;return map['lyric'];}//获取播放地址static Future<String> getLyric(MiguSongModel song) async {var copyrightId = song.originMap['copyrightId'];var path = 'http://iecoxe.top:5000/v1/migu/lyric?cid=$copyrightId';var response = await Dio().get(path);var map = response.data as Map;return map['lyric'];}}

model类做好解析

import 'package:hive/hive.dart';import 'package:listen_flutter/api/migu/apis/migu_api.dart';import 'package:listen_flutter/models/page_response.dart';import 'package:listen_flutter/models/song_model.dart';part 'migu_models.g.dart';@HiveType(typeId: 0)class MiguSongModel extends HiveObject implements SongModel {@override@HiveField(1)Map<String, dynamic> originMap; //原始数据@override@HiveField(2)String channel = "migu";MiguSongModel(this.originMap);//资源id@overrideString resourceId() {return "migu"originMap['id'];}//歌曲名@overrideString songName() {return originMap['songName'];}//歌曲图片@overrideFuture<String> songImg() {return Future.value(originMap['cover']);}//歌手名@overrideString singer() {return originMap['singerName'];}//专辑名@overrideString album() {return originMap['albumName'];}@overrideFuture<String> playUrl() {return Future.value(originMap['mp3']);}@overrideFuture<String> lyric() {return MiguApi.getLyric(this);}}class MiguPageListModel implements PageResponse {late Map<String, dynamic> originMap; //原始数据MiguPageListModel(this.originMap);@overrideList<MiguSongModel> list() {var list = originMap["musics"] as List;return list.map((e) => MiguSongModel(e)).toList();}@overridebool hasNextPage() {return true;}@overrideint nextPage() {return 1;}}

最后就是构建UI,解析歌词啥的了

// import 'package:just_audio/just_audio.dart';import 'dart:math';import 'package:audio_service/audio_service.dart';import 'package:AudioPlayers/audioplayers.dart';import 'package:get/get.dart';import 'package:get_storage/get_storage.dart';// import 'package:just_audio/just_audio.dart';import 'package:listen_flutter/models/song_model.dart';import 'package:listen_flutter/songlist/songlist.dart';import 'audio_handle.dart';class PlayerManager {var currentSong = Rx<SongModel?>(null); //当前播放歌曲var playerState = Rx<PlayerState>(PlayerState.PAUSED); //当前播放状态var position = Duration.zero.obs;var duration = Duration.zero.obs;var isInit = false.obs;var queueState = 0.obs; //0 顺序播放 1:随机播放 2:单曲循环AudioPlayer audioPlayer = AudioPlayer(); //播放器var audioHandler = Get.find<MyAudioHandler>();factory PlayerManager() => _getInstance();static PlayerManager get instance => _getInstance();static PlayerManager? _instance;PlayerManager._internal() {audioPlayer.onPlayerStateChanged.listen((event) {playerState.value = event;audioHandler.playbackState.add(audioHandler.playbackState.value.copyWith(playing: event == PlayerState.PLAYING));});audioPlayer.onAudioPositionChanged.listen((event) {position.value = event;audioHandler.playbackState.add(audioHandler.playbackState.value.copyWith(updatePosition: event));});audioPlayer.onDurationChanged.listen((event) {duration.value = event;});audioPlayer.onPlayerCompletion.listen((event) {next();});queueState.listen((value) {GetStorage().write("queueState", value);});queueState.value = GetStorage().read<int>("queueState") ?? 0;var resourceId = GetStorage().read<String>("currentSong");if (resourceId == null) {SongListManager.playList().then((value) => currentSong.value = value.first);} else {SongListManager.getSong(resourceId).then((value) => currentSong.value = value);}}static PlayerManager _getInstance() {_instance = _instance ?? PlayerManager._internal();return _instance!;}play(SongModel songModel) async {SongListManager.addSong(songModel);try {var url = await songModel.playUrl();int state = await audioPlayer.play(url);audioPlayer.getDuration();currentSong.value = songModel;GetStorage().write("currentSong", currentSong.value!.resourceId());addSong();isInit.value = true;} catch (e) {next();}}pause() {if (currentSong.value == null) {return;}audioPlayer.pause();}resume() async {if (currentSong.value == null) {return;}if (isInit.value) {audioPlayer.resume();} else {play(currentSong.value!);}}next() async {if (currentSong.value == null) {return;}var list = await SongListManager.playList();if (queueState.value == 0) {var index = list.indexOf(currentSong.value!);var i = index1;if (index >= list.length - 1) {i = 0;}var song = list.elementAt(i);currentSong.value = song;play(song);} else if (queueState.value == 1) {var random = Random();var index = random.nextInt(list.length);var song = list.elementAt(index);currentSong.value = song;play(song);} else {play(currentSong.value!);}}previous() async {if (currentSong.value == null) {return;}var list = await SongListManager.playList();if (queueState.value == 0) {var index = list.indexOf(currentSong.value!);var i = index - 1;if (index <= 0) {i = list.length - 1;}var song = list.elementAt(i);currentSong.value = song;play(song);} else if (queueState.value == 1) {var random = Random();var index = random.nextInt(list.length);var song = list.elementAt(index);currentSong.value = song;play(song);} else {play(currentSong.value!);}}seek(Duration duration) async {if (currentSong.value == null) {return;}await audioPlayer.seek(duration);}addSong() async {var songModel = currentSong.value;if (songModel == null) {return;}var img = await songModel.songImg();var album = songModel.album();var title = songModel.songName();// var duration = await audioPlayer.getDuration();var duration = 200000;var item = MediaItem(id: songModel.resourceId(),album: album,title: title,artist: '',duration: Duration(milliseconds: duration),artUri: Uri.parse(img),);// audioHandler.playMediaItem(item);audioHandler.mediaItem.add(item);var playbackState = PlaybackState(// Which buttons should appear in the notification nowcontrols: [MediaControl.skipToPrevious,MediaControl.play,MediaControl.pause,MediaControl.stop,MediaControl.skipToNext,],// Which other actions should be enabled in the notificationsystemActions: const {MediaAction.seek,MediaAction.seekForward,MediaAction.seekBackward,},// Which controls to show in Android's compact view.androidCompactActionIndices: const [0, 1, 3],// Whether audio is ready, buffering, ...processingState: AudioProcessingState.ready,// Whether audio is playingspeed: 1.0,// The current queue positionqueueIndex: 0,playing: true,);audioHandler.playbackState.add(playbackState);}}

哎,我为啥要在头条上发这种东西呢?这种东西真的有人看吗?

不管了,发几张成品图:

顺便搞了个mac版的,上班好好用 嘿嘿

虽然界面比较简陋,但是听歌的搞这么花里胡哨的干啥呢,是吧?

    推荐阅读
  • 套装门的尺寸怎么测量(套装门尺寸如何测量)

    套装门最快的安装方法1、检查洞口、拆分包装套装门三大类组成,分别是门框、门套和门扇。最后清点现场,收拾工具,撤离现场。选好颜色和款式后,还要看套装门的表面处理情况。建议最好选购家具以大品牌优先,因为这些品牌会有专门的售后服务团队,并会承诺一年内包退换等。对于小企业而言,售后方面相对不太完善,产品出现问题时,也会难以解决等。

  • 鼻炎每天打喷嚏的危害(鼻炎患者生活中牢记3点)

    不同的人对不同的过敏原过敏,这其中包括螨虫、花粉、动物皮毛、真菌、食物等等。如果有过敏性鼻炎的症状,且症状比较严重,需要到正规医院里进行诊断,确定过敏原,从而在生活中远离过敏原。除此之外,空调室与外界的温度最好不要相差超过5℃,温度相差过大也很容易诱发过敏性鼻炎。

  • 可口可乐公司退出市场(可口可乐完成史上最大规模收购)

    被收购的是运动饮料品牌BodyArmor,目前市场估值达80亿美元。而BodyArmor将成为其在目前尚未大规模布局的运动饮料赛道上,与百事可乐竞争的关键一子。不过目前,美国运动饮料市场依然由佳得乐主导,目前佳得乐占市场近70%的市场份额,BodyArmor以18%位列其后,可口可乐旗下的另一款运动饮料Powerade以13%的份额位列第三。收购完成后,可口可乐预计将占据运动饮料市场约30%的份额,当下或许很难撼动佳得乐的市场地位。

  • 省直医保什么意思(省直医保的意思)

    可以办理省直医保的包括省级党群机关,人大、政协机关,各民主党派和工商联机关以及列入参照国家公务员管理的其他单位机关工作人员和退休人员,省直国家行政机关工作人员和退休人员,省级审判机关、检察机关的工作人员和退休人员,财政全额供给及部分供给经费的省直事业单位、社会团体工作人员和退休人员。

  • 西安医学院2022考研学生情况摸排通知 西安医学院2022研究生

    西安医学院考点2022年研考考生情况摸排通知各位考生:按照《关于进一步加强2022年全国硕士研究生招生考试安全及考务工作的通知》要求,我校考点现开展考生情况摸排,请各位考生相互告知按要求配合进行,于12月19日24:00前完成。

  • 服饰颜色搭配技巧(穿衣色彩搭配小窍门)

    以上内容送给私信洛薇的可爱薇友,希望不要囿于表面,去感悟颜色背后的本质变化,方能一通百通,灵活应对。今天的时尚分享,洛薇将全面解析真冬的配色精华,以及色彩的升级运用。用浅色突出上身优势,用深色适当遮掩偏胖的腿部,完美。腿部不完美,并不意味着不能穿华丽感的下装。针对冬天的配色风格,洛薇将它们归为2类:①优雅大气风所谓大气,必定简单;所谓优雅,一定柔和。

  • 丰田rav4荣放2.5双擎试驾(沙漠试驾丰田RAV4荣放混动版)

    如果仅仅作为一款售价二十多万元的紧凑型SUV来看,它的定价并不算便宜。这也很符合这款车不完全作为一款城市SUV的定位。总结:根据在阿拉善沙漠地区对丰田RAV4荣放混动版的试驾体验,我认为这款车虽然主要定位还是家用紧凑型SUV,但是比起普通家用SUV车型,它还是具备一定的越野能力的,尤其是混动车型在低速情况下的动力表现。

  • 轮胎as和at是什么意思(at胎和ats胎的区别)

    无论是轮胎的风噪或是滤震实际效果,相对性公路胎而言是有些不足的。一般G91AT配套设施车型一般有奇骏,G91AS配套设施车型一般有长安马自达CX-5、奇骏、长安汽车。现阶段,目前市面上较为普遍的蓄电池品牌包含骆驼、理士等。理士关键生产加工多种型号规格的铅酸电池,且商品各类性能指标均已超过海外领先水准。骆驼电瓶是我国有名的蓄电池品牌,为许多车系给予正室开关电源,归属于中等水平知名品牌。

  • 2022年松鼠小镇三八妇女节游玩优惠政策

    活动一:女神带你瞰江城活动时间:3月5日——3月6日活动内容:摩天轮双人票仅需38元,仅限女性66会员购买活动二:女神畅玩小镇活动时间:3月7日——3月9日互动内容:针对所有女性主人共享,3.8元1大1小游乐项目10选1,不含摩天轮,小孩为1.4m以下活动三:女神消费狂欢活动时间:3月8日活动内容:松鼠部落、星空光影、奇妙冒险岛、冰雪世界,女性主人购票买一赠一。

  • 三星手机截屏怎么操作 三星手机手掌滑动截屏怎么操作

    以三星GalaxyS9为例,三星手机截屏操作的方法是,使用音量加电源组合键即可。三星GalaxyS9采用10纳米制程工艺八核处理器,采用4GBRAM+64GBROM的存储搭配。屏幕方面采用了一块6.2英寸QHD+SuperAMOLED显示屏;后置双1200万像素摄像头,支持光学防抖、相位对焦、2倍光学对焦;前置800万像素摄像头;3600mAh容量电池,支持QuickCharge2.0快充和无线充电,IP68防尘防水。