diff --git a/lib/ref/upnp.dart b/lib/ref/upnp.dart deleted file mode 100644 index 08368534d..000000000 --- a/lib/ref/upnp.dart +++ /dev/null @@ -1,5 +0,0 @@ -class Upnp { - static const String ssdpQueryAll = 'ssdp:all'; - static const String upnpServiceTypeAVTransport = 'urn:schemas-upnp-org:service:AVTransport:1'; - static const String upnpDeviceTypeMediaRenderer = 'urn:schemas-upnp-org:device:MediaRenderer:1'; -} diff --git a/lib/widgets/dialogs/cast_dialog.dart b/lib/widgets/dialogs/cast_dialog.dart index bd7e6257e..4ac33b993 100644 --- a/lib/widgets/dialogs/cast_dialog.dart +++ b/lib/widgets/dialogs/cast_dialog.dart @@ -1,10 +1,7 @@ -import 'dart:async'; - -import 'package:aves/ref/upnp.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:dlna_dart/dlna.dart'; import 'package:flutter/material.dart'; -import 'package:upnp2/upnp.dart'; class CastDialog extends StatefulWidget { static const routeName = '/dialog/cast'; @@ -16,60 +13,26 @@ class CastDialog extends StatefulWidget { } class _CastDialogState extends State { - final DeviceDiscoverer _discoverer = DeviceDiscoverer(); - Timer? _discoverySearchTimer; - int _queryIndex = 0; - final Map _seenRenderers = {}; + final DLNAManager _dlnaManager = DLNAManager(); + final Map _seenRenderers = {}; + + static const String upnpDeviceTypeMediaRenderer = 'urn:schemas-upnp-org:device:MediaRenderer:1'; @override void initState() { super.initState(); - _discoverClients( - [ - Upnp.ssdpQueryAll, - Upnp.upnpServiceTypeAVTransport, - Upnp.upnpDeviceTypeMediaRenderer, - ], - ).listen((client) async { - try { - final device = await client.getDevice(); - if (device != null) { - final uuid = device.uuid; - if (uuid != null && device.deviceType == Upnp.upnpDeviceTypeMediaRenderer) { - _seenRenderers[uuid] = device; - setState(() {}); - } - } - } catch (e) { - debugPrint('TLAD failed to get device e=$e'); - } + + _dlnaManager.start().then((deviceManager) { + deviceManager.devices.stream.listen((devices) { + _seenRenderers.addAll(Map.fromEntries(devices.entries.where((kv) => kv.value.info.deviceType == upnpDeviceTypeMediaRenderer))); + setState(() {}); + }); }); } - Stream _discoverClients(List queries) async* { - await _discoverer.start( - ipv6: false, - onError: (e) => debugPrint('cast: failed to start discoverer with error=$e'), - ); - _search(queries); - _discoverySearchTimer = Timer.periodic(const Duration(seconds: 5), (_) => _search(queries)); - await for (var client in _discoverer.clients) { - yield client; - } - } - - void _search(List queries) { - final searchTarget = queries[_queryIndex]; - debugPrint('cast: search target=$searchTarget'); - _discoverer.search(searchTarget); - _queryIndex = (_queryIndex + 1) % queries.length; - } - @override void dispose() { - _discoverySearchTimer?.cancel(); - _discoverySearchTimer = null; - _discoverer.stop(); + _dlnaManager.stop(); super.dispose(); } @@ -86,8 +49,8 @@ class _CastDialogState extends State { ), ), ..._seenRenderers.values.map((dev) => ListTile( - title: Text(dev.friendlyName ?? dev.uuid!), - onTap: () => Navigator.maybeOf(context)?.pop(dev), + title: Text(dev.info.friendlyName), + onTap: () => Navigator.maybeOf(context)?.pop(dev), )), ], actions: const [ diff --git a/lib/widgets/viewer/controls/cast.dart b/lib/widgets/viewer/controls/cast.dart index 50a73e724..d0219e6b4 100644 --- a/lib/widgets/viewer/controls/cast.dart +++ b/lib/widgets/viewer/controls/cast.dart @@ -2,19 +2,19 @@ import 'dart:async'; import 'dart:io'; import 'package:aves/model/entry/entry.dart'; -import 'package:aves/ref/mime_types.dart'; -import 'package:aves/ref/upnp.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/dialogs/cast_dialog.dart'; import 'package:collection/collection.dart'; +import 'package:dlna_dart/dlna.dart'; +import 'package:dlna_dart/xmlParser.dart'; import 'package:flutter/material.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; -import 'package:upnp2/upnp.dart'; mixin CastMixin { - Device? _renderer; + DLNADevice? _renderer; HttpServer? _mediaServer; bool get isCasting => _renderer != null && _mediaServer != null; @@ -25,7 +25,7 @@ mixin CastMixin { final renderer = await _selectRenderer(context); _renderer = renderer; if (renderer == null) return; - debugPrint('cast: select renderer `${renderer.friendlyName}` at ${renderer.urlBase}'); + debugPrint('cast: select renderer `${renderer.info.friendlyName}` at ${renderer.info.URLBase}'); final ip = await NetworkInfo().getWifiIP(); if (ip == null) return; @@ -66,12 +66,12 @@ mixin CastMixin { await _mediaServer?.close(); _mediaServer = null; - // await _renderer?.stop(); + await _renderer?.stop(); _renderer = null; } - Future _selectRenderer(BuildContext context) async { - return await showDialog( + Future _selectRenderer(BuildContext context) async { + return await showDialog( context: context, builder: (context) => const CastDialog(), routeSettings: const RouteSettings(name: CastDialog.routeName), @@ -85,12 +85,12 @@ mixin CastMixin { debugPrint('cast: set entry=$entry'); try { - await _setAVTransportURI( + await renderer.setUrl( '$_serverBaseUrl/${entry.id}', - entry.bestTitle ?? '${entry.id}', - entry.mimeType, + title: entry.bestTitle ?? '', + type: entry.isVideo ? PlayType.Video : PlayType.Image, ); - await _play(); + await renderer.play(); } catch (error, stack) { await reportService.recordError(error, stack); } @@ -100,36 +100,4 @@ mixin CastMixin { final server = _mediaServer; return server != null ? 'http://${server.address.host}:${server.port}' : null; } - - Future get _avTransportService async { - return _renderer!.getService(Upnp.upnpServiceTypeAVTransport); - } - - Future> _setAVTransportURI(String url, String title, String mimeType) async { - final service = await _avTransportService; - if (service == null) return {}; - - var meta = ''; - if (MimeTypes.isVideo(mimeType)) { - meta = '''$titleunkownobject.item.videoItem'''; - } else if (MimeTypes.isImage(mimeType)) { - meta = '''$titleunkownobject.item.imageItem'''; - } - var args = { - 'InstanceID': 0, - 'CurrentURI': url, - 'CurrentURIMetaData': meta, - }; - return service.invokeAction('SetAVTransportURI', args); - } - - Future> _play() async { - final service = await _avTransportService; - if (service == null) return {}; - - return service.invokeAction('Play', { - 'InstanceID': 0, - 'Speed': 1, - }); - } } diff --git a/pubspec.lock b/pubspec.lock index 712dca187..ff4437047 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -302,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + dlna_dart: + dependency: "direct main" + description: + name: dlna_dart + sha256: ae07c1c53077bbf58756fa589f936968719b0085441981d33e74f82f89d1d281 + url: "https://pub.dev" + source: hosted + version: "0.0.8" dynamic_color: dependency: "direct main" description: @@ -1543,14 +1551,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0+1" - upnp2: - dependency: "direct main" - description: - name: upnp2 - sha256: "60902d702809a9802ed6fe953cf8ebebfbab1ef8d99e516665b74b6a4522cad4" - url: "https://pub.dev" - source: hosted - version: "3.0.11" uri_parser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3a3ff4b85..d26fc9ddc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -117,7 +117,7 @@ dependencies: volume_controller: xml: - upnp2: + dlna_dart: network_info_plus: shelf: