YY播放器使用Flutter编写的一个聚合播放器, 起因是看了 ZY-Player的源码, 发现实现挺有意思的, 也比较简单
地址: https://github.com/waifu-project/movie
下载源码之后, 首先从入口函数入手
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await XHttp.init();
await GetStorage.init();
await MirrorManage.init();
final localStorage = GetStorage();
bool isDark = (localStorage.read(ConstDart.ls_isDark) ?? false);
bool systemBrightnessFlag = (localStorage.read(ConstDart.auto_dark) ?? false);
Brightness wrapperIfDark = Brightness.light;
{
if (isDark) wrapperIfDark = Brightness.dark;
if (GetPlatform.isWindows && systemBrightnessFlag) {
var windowMode = getWindowsThemeMode();
wrapperIfDark = windowMode;
}
if (GetPlatform.isDesktop) {
doWhenWindowReady(() {
final initialSize = Size(990, 720);
appWindow.minSize = initialSize;
appWindow.size = initialSize;
appWindow.alignment = Alignment.center;
appWindow.show();
});
}
}
大概可以看出支持了 windows
平台(从 doWhenWindowReady
回调可以看出)
继续从依赖中猜一下大概实现方式
dependencies:
cupertino_icons: ^1.0.2 # 图标
get: 4.3.8 # 状态管理
salomon_bottom_bar: ^3.1.0
dio: ^4.0.0 # 网络库
cookie_jar: ^3.0.1
dio_cookie_manager: ^2.0.0
html: ^0.15.0
flutter_cupertino_settings: ^0.5.0
webview_flutter: ^2.1.1 # webview组件
flappy_search_bar_ns: ^2.0.2
xml2json: ^5.3.1 # xml to json
modal_bottom_sheet: ^2.0.0
pull_to_refresh: ^2.0.0
cupertino_list_tile: ^0.2.0
cached_network_image: ^3.1.0
chewie: ^1.2.2 # 视频播放
video_player: ^2.2.5
get_storage: ^2.0.3
path_provider: ^2.0.6
flutter_html: ^2.1.5
clipboard: ^0.1.3
wakelock: ^0.5.6
auto_orientation: ^2.1.0
desktop_webview_window: ^0.0.5
bitsdojo_window: ^0.1.1+1
url_launcher: ^6.0.12
首先是所谓的聚合
实现
在 lib/impl/movie.dart
中有一个抽象类
abstract class MovieImpl {
/// 源信息
MovieMetaData get meta;
/// 获取首页
Future<List<MirrorOnceItemSerialize>> getHome({
int page = 1,
int limit = 10,
});
/// 搜索
Future<List<MirrorOnceItemSerialize>> getSearch({
required String keyword,
int page = 1,
int limit = 10,
});
/// 获取视频详情
Future<MirrorOnceItemSerialize> getDetail(String movie_id);
}
基本上源的操作就那几个:
- 首页
- 搜索
- 详情
接下来在看一下 MirrorOnceItemSerialize
, 就是定义的一个标准实体类
class MirrorOnceItemSerialize {
/// id
final String id;
/// 标题
final String title;
/// 介绍
final String desc;
/// 喜欢
final int likeCount;
/// 访问人数
final int viewCount;
/// 不喜欢
final int dislikeCount;
/// 小封面图(必须要有)
final String smallCoverImage;
/// 大封面图
final String bigCoverImage;
/// 视频列表
final List<MirrorSerializeVideoInfo> videos;
/// 视频信息
/// 视频尺寸大小
/// 视频长度大小
final MirrorSerializeVideoSize videoInfo;
}
接下来就是实现这个抽象类, 比如内置的奈菲
源, 来看一下其的实现方式
@override
Future<MirrorOnceItemSerialize> getDetail(String movie_id) async {
var detailURL = createURL(path: "/detail/" + movie_id);
var resp = await XHttp.dio.get(
detailURL,
options: Options(
headers: header,
),
);
var parse = html.parse(resp.data);
var ele = parse.querySelector(".myui-panel_hd");
if (ele == null) throw UnimplementedError();
var mirrorList = ele.querySelectorAll('li');
List<fetchMovieFrameURL> frames = [];
mirrorList.map((e) {
// ...(忽略代码)
return bat;
}).toList();
var data = await Future.wait<MirrorSerializeVideoInfo>(frames.map(
(e) async {
var url = await findIframeM3u8URL(e.id);
var title = e.title;
var item = MirrorSerializeVideoInfo(
url: url,
type: KBaseMirrorMovie.easyGetVideoType(url),
name: title,
);
return item;
},
).toList());
var infoEle = parse.querySelector(
'.myui-vodlist__thumb.img-md-220.img-xs-130.picture',
);
var coverImage = infoEle!.querySelector("img")?.attributes['data-original'] ?? "";
var title = infoEle.attributes['title'] ?? "";
return MirrorOnceItemSerialize(
id: movie_id,
smallCoverImage: coverImage,
title: title,
videos: data,
);
}
相信同学们基本上看明白了: 这个奈菲源
的实现是通过解析 html
然后拿到数据
所以说, 只要你实现了视频源抽象类, 你想怎么玩就怎么玩(所以快来PR添加资源吧)
同样的, 根据这个抽象类, 实现了 ZY-Player
的源, 单个资源数据类型
{
"key": "快播云",
"id": 1,
"name": "快播云",
"api": "http://www.kuaibozy.com/api.php/provide/vod/from/kbm3u8/at/xml/",
"download": "",
"jiexiUrl": "https://jx.7kjx.com/?url=",
"group": "默认",
"isActive": true,
"status": "可用",
"reverseOrder": true
}
继续扒一下 ZF-Player
的源码: https://github.com/cuiocean/ZY-Player/blob/master/src/lib/site/tools.js
就可以找到规则:
首页就是: $ROOT?ac=videolist&pg=${pg}
/**
* 获取资源列表
* @param {*} key 资源网 key
* @param {number} [pg=1] 翻页 page
* @param {*} t 分类 type
* @returns
*/
list (key, pg = 1, t) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
let url = null
if (t) {
url = `${site.api}?ac=videolist&t=${t}&pg=${pg}`
} else {
url = `${site.api}?ac=videolist&pg=${pg}`
}
// todo
})
})
},
详情就是: $ROOT?ac=videolist&ids=${id}
/**
* 获取资源详情
* @param {*} key 资源网 key
* @param {*} id 资源唯一标识符 id
* @returns
*/
detail (key, id) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = `${res.api}?ac=videolist&ids=${id}`
// TODO
})
}
搜索就是: $ROOT?wd=${wd}
/**
* 搜索资源
* @param {*} key 资源网 key
* @param {*} wd 搜索关键字
* @returns
*/
search (key, wd) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
const url = `${site.api}?wd=${encodeURI(wd)}`
// TODO
})
})
}
根据此规则就可以兼容 ZY-Player
的资源
后续
大概实现方式这样, 比较简单, 后续加一点点细节就可以了
有兴趣的同学可以看一下源码: https://github.com/waifu-project/movie
mail: chenhonzhou@gmail.com