diff --git a/lib/pages/dlna/index.dart b/lib/pages/dlna/index.dart new file mode 100644 index 00000000..3ec5965b --- /dev/null +++ b/lib/pages/dlna/index.dart @@ -0,0 +1,111 @@ +import 'dart:async'; + +import 'package:dlna_dart/dlna.dart'; +import 'package:flutter/material.dart'; + +class LiveDlnaPage extends StatefulWidget { + final String datasource; + + const LiveDlnaPage({Key? key, required this.datasource}) : super(key: key); + + @override + State createState() => _LiveDlnaPageState(); +} + +class _LiveDlnaPageState extends State { + final Map _deviceList = {}; + final DLNAManager searcher = DLNAManager(); + late final Timer stopSearchTimer; + String selectDeviceKey = ''; + bool isSearching = true; + + DLNADevice? get device => _deviceList[selectDeviceKey]; + + @override + void initState() { + stopSearchTimer = Timer(const Duration(seconds: 20), () { + setState(() => isSearching = false); + searcher.stop(); + }); + searcher.stop(); + startSearch(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + searcher.stop(); + stopSearchTimer.cancel(); + } + + void startSearch() async { + // clear old devices + isSearching = true; + selectDeviceKey = ''; + _deviceList.clear(); + setState(() {}); + // start search server + final m = await searcher.start(); + m.devices.stream.listen((deviceList) { + deviceList.forEach((key, value) { + _deviceList[key] = value; + }); + setState(() {}); + }); + // close the server, the closed server can be start by call searcher.start() + } + + void selectDevice(String key) { + if (selectDeviceKey.isNotEmpty) device?.pause(); + + selectDeviceKey = key; + device?.setUrl(widget.datasource); + device?.play(); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + Widget cur; + if (isSearching && _deviceList.isEmpty) { + cur = const Center(child: CircularProgressIndicator()); + } else if (_deviceList.isEmpty) { + cur = Center( + child: Text( + '没有找到设备', + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } else { + cur = ListView( + children: _deviceList.keys + .map((key) => ListTile( + contentPadding: const EdgeInsets.all(2), + title: Text(_deviceList[key]!.info.friendlyName), + subtitle: Text(key), + onTap: () => selectDevice(key), + )) + .toList(), + ); + } + + return AlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('查找设备'), + IconButton( + onPressed: startSearch, + icon: const Icon(Icons.refresh_rounded), + ), + ], + ), + content: SizedBox( + height: 200, + width: 200, + child: cur, + ), + ); + } +} diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 4a8f4759..7a12b0cf 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -11,6 +11,7 @@ import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/url.dart'; +import 'package:pilipala/pages/dlna/index.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; @@ -1209,6 +1210,22 @@ class _HeaderControlState extends State { // ), // fuc: () => _.screenshot(), // ), + ComBtn( + icon: const Icon( + Icons.cast, + size: 19, + color: Colors.white, + ), + fuc: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return LiveDlnaPage( + datasource: widget.videoDetailCtr!.videoUrl); + }, + ); + }, + ), if (isFullScreen.value) ...[ SizedBox( width: 56, diff --git a/pubspec.lock b/pubspec.lock index 84556c06..a64c85c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -409,6 +409,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" + dlna_dart: + dependency: "direct main" + description: + name: dlna_dart + sha256: ae07c1c53077bbf58756fa589f936968719b0085441981d33e74f82f89d1d281 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.8" dynamic_color: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 67b0b9b8..4402b646 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -144,6 +144,8 @@ dependencies: disable_battery_optimization: ^1.1.1 # 展开/收起 expandable: ^5.0.1 + # 投屏 + dlna_dart: ^0.0.8 dev_dependencies: flutter_test: