前面我發了一篇文章,向大家推薦了一個聽歌軟件: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 = ""; //渠道
//資源id
String 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 "";
}
}
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'];
}
}
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
@override
String resourceId() {
return "migu" originMap['id'];
}
//歌曲名
@override
String songName() {
return originMap['songName'];
}
//歌曲圖片
@override
Future<String> songImg() {
return Future.value(originMap['cover']);
}
//歌手名
@override
String singer() {
return originMap['singerName'];
}
//專輯名
@override
String album() {
return originMap['albumName'];
}
@override
Future<String> playUrl() {
return Future.value(originMap['mp3']);
}
@override
Future<String> lyric() {
return MiguApi.getLyric(this);
}
}
class MiguPageListModel implements PageResponse {
late Map<String, dynamic> originMap; //原始數據
MiguPageListModel(this.originMap);
@override
List<MiguSongModel> list() {
var list = originMap["musics"] as List;
return list.map((e) => MiguSongModel(e)).toList();
}
@override
bool hasNextPage() {
return true;
}
@override
int nextPage() {
return 1;
}
}
// 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 = index 1;
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 now
controls: [
MediaControl.skipToPrevious,
MediaControl.play,
MediaControl.pause,
MediaControl.stop,
MediaControl.skipToNext,
],
// Which other actions should be enabled in the notification
systemActions: 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 playing
speed: 1.0,
// The current queue position
queueIndex: 0,
playing: true,
);
audioHandler.playbackState.add(playbackState);
}
}
哎,我為啥要在頭條上發這種東西呢?這種東西真的有人看嗎?
不管了,發幾張成品圖:
順便搞了個mac版的,上班好好用 嘿嘿
雖然界面比較簡陋,但是聽歌的搞這麼花裡胡哨的幹啥呢,是吧?
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!