#678 video: media_kit player POC

This commit is contained in:
Thibault Deckers 2023-07-05 22:56:36 +02:00
parent ad74eef150
commit 46c58207e7
47 changed files with 1138 additions and 423 deletions

View file

@ -1,7 +1,7 @@
buildscript {
ext {
kotlin_version = '1.8.21'
agp_version = '8.0.2'
agp_version = '7.4.2'
glide_version = '4.15.1'
// AppGallery Connect plugin versions: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-sdk-changenotes-0000001058732550
// TODO TLAD AppGallery Connect plugin v1.9.0.300 does not support Gradle 8+

View file

@ -200,6 +200,7 @@ class AvesEntry with AvesEntryBase {
_bestTitle = null;
}
@override
String? get path => _path;
// directory path, without the trailing separator

View file

@ -71,7 +71,7 @@ extension ExtraAvesEntryInfo on AvesEntry {
Future<List<MetadataDirectory>> _getStreamDirectories(BuildContext context) async {
final directories = <MetadataDirectory>[];
final mediaInfo = await VideoMetadataFormatter.getVideoMetadata(this);
final mediaInfo = await videoMetadataFetcher.getMetadata(this);
final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo);
if (formattedMediaTags.isNotEmpty) {

View file

@ -16,13 +16,10 @@ import 'package:aves/utils/math_utils.dart';
import 'package:aves/utils/string_utils.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_video_ijk/aves_video_ijk.dart';
import 'package:collection/collection.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart';
class VideoMetadataFormatter {
static bool _initializedFijkLog = false;
static final _dateY4M2D2H2m2s2Pattern = RegExp(r'(\d{4})[-./:](\d{1,2})[-./:](\d{1,2})([ T](\d{1,2}):(\d{1,2}):(\d{1,2})( ([ap]\.? ?m\.?))?)?');
static final _ambiguousDatePatterns = {
RegExp(r'^\d{2}[-/]\d{2}[-/]\d{4}$'),
@ -45,24 +42,8 @@ class VideoMetadataFormatter {
Codecs.webm: 'WebM',
};
static Future<Map> getVideoMetadata(AvesEntry entry) async {
if (!_initializedFijkLog) {
_initializedFijkLog = true;
FijkLog.setLevel(FijkLogLevel.Warn);
}
final player = FijkPlayer();
final info = await player.setDataSourceUntilPrepared(entry.uri).then((v) {
return player.getInfo();
}).catchError((error) {
debugPrint('failed to get video metadata for entry=$entry, error=$error');
return {};
});
await player.release();
return info;
}
static Future<Map<String, int>> getLoadingMetadata(AvesEntry entry) async {
final mediaInfo = await getVideoMetadata(entry);
final mediaInfo = await videoMetadataFetcher.getMetadata(entry);
final fields = <String, int>{};
final streams = mediaInfo[Keys.streams];
@ -87,7 +68,7 @@ class VideoMetadataFormatter {
}
static Future<CatalogMetadata?> getCatalogMetadata(AvesEntry entry) async {
final mediaInfo = await getVideoMetadata(entry);
final mediaInfo = await videoMetadataFetcher.getMetadata(entry);
// only consider values with at least 8 characters (yyyymmdd),
// ignoring unset values like `0`, as well as year values like `2021`

View file

@ -19,6 +19,8 @@ import 'package:aves_report/aves_report.dart';
import 'package:aves_report_platform/aves_report_platform.dart';
import 'package:aves_services/aves_services.dart';
import 'package:aves_services_platform/aves_services_platform.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_ijk/aves_video_ijk.dart';
import 'package:get_it/get_it.dart';
import 'package:path/path.dart' as p;
@ -30,6 +32,8 @@ final SettingsStore settingsStore = SharedPrefSettingsStore();
final p.Context pContext = getIt<p.Context>();
final AvesAvailability availability = getIt<AvesAvailability>();
final MetadataDb metadataDb = getIt<MetadataDb>();
final AvesVideoControllerFactory videoControllerFactory = getIt<AvesVideoControllerFactory>();
final AvesVideoMetadataFetcher videoMetadataFetcher = getIt<AvesVideoMetadataFetcher>();
final AppService appService = getIt<AppService>();
final DeviceService deviceService = getIt<DeviceService>();
@ -50,6 +54,8 @@ void initPlatformServices() {
getIt.registerLazySingleton<p.Context>(p.Context.new);
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
getIt.registerLazySingleton<AvesVideoControllerFactory>(IjkVideoControllerFactory.new);
getIt.registerLazySingleton<AvesVideoMetadataFetcher>(IjkVideoMetadataFetcher.new);
getIt.registerLazySingleton<AppService>(PlatformAppService.new);
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);

View file

@ -455,6 +455,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
await _onTvLayoutChanged();
_monitorSettings();
videoControllerFactory.init();
unawaited(_setupErrorReporting());

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/location.dart';
@ -56,7 +57,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoTogglePlay:
await _togglePlayPause(context, controller);
case EntryAction.videoReplay10:
await controller.seekTo(controller.currentPosition - 10000);
await controller.seekTo(max(controller.currentPosition - 10000, 0));
case EntryAction.videoSkip10:
await controller.seekTo(controller.currentPosition + 10000);
case EntryAction.openVideo:
@ -189,6 +190,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
}
Future<void> _togglePlayPause(BuildContext context, AvesVideoController controller) async {
if (!context.mounted) return;
if (controller.isPlaying) {
await controller.pause();
} else {

View file

@ -579,11 +579,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
required AvesVideoController controller,
required EntryAction action,
}) async {
await _videoActionDelegate.onActionSelected(context, entry, controller, action);
if (action == EntryAction.videoToggleMute) {
final override = controller.isMuted;
final override = !controller.isMuted;
videoMutedOverride = override;
await context.read<VideoConductor>().muteAll(override);
} else {
await _videoActionDelegate.onActionSelected(context, entry, controller, action);
}
}

View file

@ -8,7 +8,6 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/viewer/video/db_playback_state_handler.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_ijk/aves_video_ijk.dart';
import 'package:collection/collection.dart';
class VideoConductor {
@ -37,7 +36,7 @@ class VideoConductor {
if (controller != null) {
_controllers.remove(controller);
} else {
controller = IjkPlayerAvesVideoController(
controller = videoControllerFactory.buildController(
entry,
playbackStateHandler: playbackStateHandler,
settings: settings,

View file

@ -1,107 +0,0 @@
// import 'dart:async';
// import 'dart:io';
//
// import 'package:aves/model/entry.dart';
// import 'package:aves_utils/aves_utils.dart';
// import 'package:aves_video/aves_video.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter/src/foundation/change_notifier.dart';
// import 'package:flutter/src/widgets/framework.dart';
// import 'package:flutter_vlc_player/flutter_vlc_player.dart';
// import 'package:provider/provider.dart';
//
// class VlcAvesVideoController extends AvesVideoController {
// VlcPlayerController _instance;
// final List<StreamSubscription> _subscriptions = [];
// final StreamController<VlcPlayerValue> _valueStreamController = StreamController.broadcast();
// final AChangeNotifier _playFinishNotifier = AChangeNotifier();
//
// Stream<VlcPlayerValue> get _valueStream => _valueStreamController.stream;
//
// VlcAvesVideoController();
//
// @override
// Future<void> setDataSource(String uri, {int startMillis = 0}) async {
// _instance = VlcPlayerController.file(
// File(uri),
// );
// _instance.addListener(_onValueChanged);
// _subscriptions.add(_valueStream.where((value) => value.isEnded).listen((_) => _playFinishNotifier.notifyListeners()));
//
// // update value stream to:
// // 1) trigger playability check
// // 2) show the `VlcPlayer` widget
// // 3) initialize its `PlatformView`
// // 4) complete `VlcPlayerController` initialization
// _valueStreamController.add(_instance.value);
// }
//
// @override
// void dispose() {
// _instance?.removeListener(_onValueChanged);
// _valueStreamController.close();
// _subscriptions
// ..forEach((sub) => sub.cancel())
// ..clear();
// _instance?.dispose();
// }
//
// void _onValueChanged() => _valueStreamController.add(_instance.value);
//
// @override
// Future<void> play() => _instance.play();
//
// @override
// Future<void> pause() => _instance?.pause();
//
// @override
// Future<void> seekTo(int targetMillis) => _instance.seekTo(Duration(milliseconds: targetMillis));
//
// @override
// Listenable get playCompletedListenable => _playFinishNotifier;
//
// @override
// VideoStatus get status => _instance?.value?.toAves ?? VideoStatus.idle;
//
// @override
// Stream<VideoStatus> get statusStream => _valueStream.map((value) => value.toAves);
//
// @override
// bool get isPlayable => _instance != null;
//
// @override
// int get duration => _instance?.value?.duration?.inMilliseconds;
//
// @override
// int get currentPosition => _instance?.value?.position?.inMilliseconds;
//
// @override
// Stream<int> get positionStream => _valueStream.map((value) => value.position.inMilliseconds);
//
// @override
// Widget buildPlayerWidget(BuildContext context, AvesEntry entry) {
// // do not use `Magnifier` with `applyScale` enabled when using this widget,
// // as the original video size will be used to create the `PlatformView`
// // and a virtual display larger than the device screen may crash the app
// final mqWidth = MediaQuery.sizeOf(context).width;
// final displaySize = entry.displaySize;
// final ratio = mqWidth / displaySize.width;
// return SizedBox.fromSize(
// size: displaySize * ratio,
// child: VlcPlayer(
// controller: _instance,
// aspectRatio: entry.displayAspectRatio,
// ),
// );
// }
// }
//
// extension ExtraVlcPlayerValue on VlcPlayerValue {
// VideoStatus get toAves {
// if (hasError) return VideoStatus.error;
// if (!isInitialized) return VideoStatus.idle;
// if (isEnded) return VideoStatus.completed;
// if (isPlaying) return VideoStatus.playing;
// return VideoStatus.paused;
// }
// }

View file

@ -1,83 +0,0 @@
// import 'dart:async';
//
// import 'package:aves/model/entry.dart';
// import 'package:aves_utils/aves_utils.dart';
// import 'package:aves_video/aves_video.dart';
// import 'package:flutter/src/foundation/change_notifier.dart';
// import 'package:flutter/src/widgets/framework.dart';
// import 'package:video_player/video_player.dart';
//
// class VideoPlayerAvesVideoController extends AvesVideoController {
// VideoPlayerController _instance;
// final List<StreamSubscription> _subscriptions = [];
// final StreamController<VideoPlayerValue> _valueStreamController = StreamController.broadcast();
// final AChangeNotifier _playFinishNotifier = AChangeNotifier();
//
// Stream<VideoPlayerValue> get _valueStream => _valueStreamController.stream;
//
// VideoPlayerAvesVideoController();
//
// @override
// Future<void> setDataSource(String uri, {int startMillis = 0}) async {
// _instance = VideoPlayerController.network(uri);
// _instance.addListener(_onValueChanged);
// _subscriptions.add(_valueStream.where((value) => value.position > value.duration).listen((_) => _playFinishNotifier.notifyListeners()));
//
// await _instance.initialize();
// await play();
// }
//
// @override
// void dispose() {
// _instance?.removeListener(_onValueChanged);
// _valueStreamController.close();
// _subscriptions
// ..forEach((sub) => sub.cancel())
// ..clear();
// _instance?.dispose();
// }
//
// void _onValueChanged() => _valueStreamController.add(_instance.value);
//
// @override
// Future<void> play() => _instance.play();
//
// @override
// Future<void> pause() => _instance?.pause();
//
// @override
// Future<void> seekTo(int targetMillis) => _instance.seekTo(Duration(milliseconds: targetMillis));
//
// @override
// Listenable get playCompletedListenable => _playFinishNotifier;
//
// @override
// VideoStatus get status => _instance?.value?.toAves ?? VideoStatus.idle;
//
// @override
// Stream<VideoStatus> get statusStream => _valueStream.map((value) => value.toAves);
//
// @override
// bool get isPlayable => _instance != null && _instance.value.isInitialized && !_instance.value.hasError;
//
// @override
// int get duration => _instance?.value?.duration?.inMilliseconds;
//
// @override
// int get currentPosition => _instance?.value?.position?.inMilliseconds;
//
// @override
// Stream<int> get positionStream => _valueStream.map((value) => value.position.inMilliseconds);
//
// @override
// Widget buildPlayerWidget(BuildContext context, AvesEntry entry) => VideoPlayer(_instance);
// }
//
// extension ExtraVideoPlayerValue on VideoPlayerValue {
// VideoStatus get toAves {
// if (hasError) return VideoStatus.error;
// if (!isInitialized) return VideoStatus.idle;
// if (isPlaying) return VideoStatus.playing;
// return VideoStatus.paused;
// }
// }

View file

@ -128,7 +128,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
if (videoAutoPlayEnabled) {
final resumeTimeMillis = await controller.getResumeTime(context);
await _playVideo(controller, () => entry == entryNotifier.value, resumeTimeMillis: resumeTimeMillis);
await _autoPlayVideo(controller, () => entry == entryNotifier.value, resumeTimeMillis: resumeTimeMillis);
}
}
@ -163,7 +163,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
final pageVideoController = videoConductor.getController(pageEntry);
assert(pageVideoController != null);
if (pageVideoController != null) {
await _playVideo(pageVideoController, () => entry == entryNotifier.value && page == multiPageController.page);
await _autoPlayVideo(pageVideoController, () => entry == entryNotifier.value && page == multiPageController.page);
}
}
}
@ -192,7 +192,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
}
}
Future<void> _playVideo(AvesVideoController videoController, bool Function() isCurrent, {int? resumeTimeMillis}) async {
Future<void> _autoPlayVideo(AvesVideoController videoController, bool Function() isCurrent, {int? resumeTimeMillis}) async {
// video decoding may fail or have initial artifacts when the player initializes
// during this widget initialization (because of the page transition and hero animation?)
// so we play after a delay for increased stability
@ -204,9 +204,8 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
if (resumeTimeMillis != null) {
await videoController.seekTo(resumeTimeMillis);
} else {
await videoController.play();
}
await videoController.play();
// playing controllers are paused when the entry changes,
// but the controller may still be preparing (not yet playing) when this happens

View file

@ -41,10 +41,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -73,10 +73,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_map:
dependency: "direct main"
description:
@ -89,10 +89,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.1.0"
http_parser:
dependency: transitive
description:

View file

@ -9,6 +9,8 @@ mixin AvesEntryBase {
int? get pageId;
String? get path;
int? get sizeBytes;
int? get durationMillis;

View file

@ -34,10 +34,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -26,10 +26,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -26,10 +26,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -33,10 +33,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -113,10 +113,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_test:
dependency: transitive
description: flutter

View file

@ -26,10 +26,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -80,10 +80,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_map:
dependency: transitive
description:
@ -96,10 +96,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.1.0"
http_parser:
dependency: transitive
description:

View file

@ -127,10 +127,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_map:
dependency: transitive
description:
@ -220,10 +220,10 @@ packages:
dependency: transitive
description:
name: google_maps_flutter_web
sha256: "5f58d7c491240b0074f455e70ce8d9b038f92472559e49e3b611d9f39b8d51a7"
sha256: "280170a2dcac3364317b5786f0d2e3c4128fdb795bc0d87ffe56226b0cf1f57d"
url: "https://pub.dev"
source: hosted
version: "0.5.0+1"
version: "0.5.1"
html:
dependency: transitive
description:
@ -236,10 +236,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.1.0"
http_parser:
dependency: transitive
description:
@ -441,10 +441,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "1414f27dd781737e51afa9711f2ac2ace6ab4498ee98e20863fa5505aa00c58c"
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
url: "https://pub.dev"
source: hosted
version: "5.0.4"
version: "5.0.5"
win32_registry:
dependency: transitive
description:

View file

@ -94,10 +94,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_map:
dependency: transitive
description:
@ -110,10 +110,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.1.0"
http_parser:
dependency: transitive
description:

View file

@ -87,10 +87,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_map:
dependency: transitive
description:
@ -103,10 +103,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.1.0"
http_parser:
dependency: transitive
description:

View file

@ -26,10 +26,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -26,10 +26,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -1,6 +1,7 @@
library aves_video;
export 'src/controller.dart';
export 'src/metadata.dart';
export 'src/settings/subtitles.dart';
export 'src/settings/video.dart';
export 'src/stream.dart';

View file

@ -1,11 +1,20 @@
import 'dart:async';
import 'package:aves_model/aves_model.dart';
import 'package:aves_video/src/settings/video.dart';
import 'package:aves_video/src/stream.dart';
import 'package:aves_video/aves_video.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
abstract class AvesVideoControllerFactory {
void init();
AvesVideoController buildController(
AvesEntryBase entry, {
required PlaybackStateHandler playbackStateHandler,
required VideoSettings settings,
});
}
abstract class AvesVideoController {
final AvesEntryBase _entry;
final PlaybackStateHandler playbackStateHandler;
@ -44,7 +53,7 @@ abstract class AvesVideoController {
Future<void> seekTo(int targetMillis);
Future<void> seekToProgress(double progress) => seekTo((duration * progress).toInt());
Future<void> seekToProgress(double progress) => seekTo((duration * progress.clamp(0, 1)).toInt());
Listenable get playCompletedListenable;
@ -58,6 +67,18 @@ abstract class AvesVideoController {
bool get isReady;
Future<void> get untilReady {
if (isReady) return Future.value();
final completer = Completer();
late StreamSubscription<VideoStatus> sub;
sub = statusStream.where((_) => isReady).listen((_) {
sub.cancel();
completer.complete();
});
return completer.future;
}
bool get isPlaying => status == VideoStatus.playing;
int get duration;

View file

@ -0,0 +1,9 @@
import 'dart:async';
import 'package:aves_model/aves_model.dart';
abstract class AvesVideoMetadataFetcher {
void init();
Future<Map> getMetadata(AvesEntryBase entry);
}

View file

@ -18,5 +18,5 @@ class MediaStreamSummary {
});
@override
String toString() => '$runtimeType#${shortHash(this)}{type: type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}';
String toString() => '$runtimeType#${shortHash(this)}{type: $type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}';
}

View file

@ -48,10 +48,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

View file

@ -1,3 +1,5 @@
library aves_video_ijk;
export 'src/controller.dart';
export 'src/factory.dart';
export 'src/metadata.dart';

View file

@ -8,9 +8,7 @@ import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class IjkPlayerAvesVideoController extends AvesVideoController {
static bool _initializedFijkLog = false;
class IjkVideoController extends AvesVideoController {
final EventChannel _eventChannel = const OptionalEventChannel('befovy.com/fijk/event');
late FijkPlayer _instance;
@ -21,7 +19,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
final StreamController<double> _speedStreamController = StreamController.broadcast();
final AChangeNotifier _completedNotifier = AChangeNotifier();
Offset _macroBlockCrop = Offset.zero;
final List<MediaStreamSummary> _streams = [];
Timer? _initialPlayTimer;
double _speed = 1;
double _volume = 1;
@ -58,15 +55,11 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
static const gifLikeBitRateThreshold = 2 << 18; // 512kB/s (4Mb/s)
static const captureFrameEnabled = true;
IjkPlayerAvesVideoController(
IjkVideoController(
super.entry, {
required super.playbackStateHandler,
required super.settings,
}) {
if (!_initializedFijkLog) {
_initializedFijkLog = true;
FijkLog.setLevel(FijkLogLevel.Warn);
}
_instance = FijkPlayer();
_valueStream.map((value) => value.videoRenderStart).firstWhere((v) => v, orElse: () => false).then(
(started) {
@ -123,7 +116,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
}
sarNotifier.value = 1;
_streams.clear();
streams.clear();
_applyOptions(startMillis);
// calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts
@ -247,56 +240,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
return true;
}
void _fetchStreams() async {
final mediaInfo = await _instance.getInfo();
if (!mediaInfo.containsKey(Keys.streams)) return;
var videoStreamCount = 0, audioStreamCount = 0, textStreamCount = 0;
_streams.clear();
final allStreams = (mediaInfo[Keys.streams] as List).cast<Map>();
allStreams.forEach((stream) {
final type = ExtraStreamType.fromTypeString(stream[Keys.streamType]);
if (type != null) {
final width = stream[Keys.width] as int?;
final height = stream[Keys.height] as int?;
_streams.add(MediaStreamSummary(
type: type,
index: stream[Keys.index],
codecName: stream[Keys.codecName],
language: stream[Keys.language],
title: stream[Keys.title],
width: width,
height: height,
));
switch (type) {
case MediaStreamType.video:
// check width/height to exclude image streams (that are included among video streams)
if (width != null && height != null) {
videoStreamCount++;
}
case MediaStreamType.audio:
audioStreamCount++;
case MediaStreamType.text:
textStreamCount++;
}
}
});
canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0;
final selectedVideo = await getSelectedStream(MediaStreamType.video);
if (selectedVideo != null) {
final streamIndex = selectedVideo.index;
final streamInfo = allStreams.firstWhereOrNull((stream) => stream[Keys.index] == streamIndex);
if (streamInfo != null) {
final num = streamInfo[Keys.sarNum] ?? 0;
final den = streamInfo[Keys.sarDen] ?? 0;
sarNotifier.value = (num != 0 ? num : 1) / (den != 0 ? den : 1);
}
}
}
// cf https://developer.android.com/reference/android/media/AudioManager
static const int _audioFocusLoss = -1;
static const int _audioFocusRequestFailed = 0;
@ -321,7 +264,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
}
void _onValueChanged() {
if (_instance.state == FijkState.prepared && _streams.isEmpty) {
if (_instance.state == FijkState.prepared && streams.isEmpty) {
_fetchStreams();
}
_valueStreamController.add(_instance.value);
@ -427,39 +370,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
// TODO TLAD [video] bug: setting speed fails when there is no audio stream or audio is disabled
Future<void> _applySpeed() => _instance.setSpeed(speed);
// When a stream is selected, the video accelerates to catch up with it.
// The duration of this acceleration phase depends on the player `min-frames` parameter.
// Calling `seekTo` after stream de/selection is a workaround to:
// 1) prevent video stream acceleration to catch up with audio
// 2) apply timed text stream
@override
Future<void> selectStream(MediaStreamType type, MediaStreamSummary? selected) async {
final current = await getSelectedStream(type);
if (current != selected) {
if (selected != null) {
final newIndex = selected.index;
if (newIndex != null) {
await _instance.selectTrack(newIndex);
}
} else if (current != null) {
await _instance.deselectTrack(current.index!);
}
if (type == MediaStreamType.text) {
_timedTextStreamController.add(null);
}
await seekTo(currentPosition);
}
}
@override
Future<MediaStreamSummary?> getSelectedStream(MediaStreamType type) async {
final currentIndex = await _instance.getSelectedTrack(type.code);
return currentIndex != -1 ? _streams.firstWhereOrNull((v) => v.index == currentIndex) : null;
}
@override
List<MediaStreamSummary> get streams => _streams;
@override
Future<Uint8List> captureFrame() {
if (!_instance.value.videoRenderStart) {
@ -503,6 +413,93 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
return Alignment.topLeft;
}
}
// streams (aka tracks)
final List<MediaStreamSummary> _streams = [];
@override
List<MediaStreamSummary> get streams => _streams;
void _fetchStreams() async {
final mediaInfo = await _instance.getInfo();
if (!mediaInfo.containsKey(Keys.streams)) return;
var videoStreamCount = 0, audioStreamCount = 0, textStreamCount = 0;
_streams.clear();
final allStreams = (mediaInfo[Keys.streams] as List).cast<Map>();
allStreams.forEach((stream) {
final type = ExtraStreamType.fromTypeString(stream[Keys.streamType]);
if (type != null) {
final width = stream[Keys.width] as int?;
final height = stream[Keys.height] as int?;
_streams.add(MediaStreamSummary(
type: type,
index: stream[Keys.index],
codecName: stream[Keys.codecName],
language: stream[Keys.language],
title: stream[Keys.title],
width: width,
height: height,
));
switch (type) {
case MediaStreamType.video:
// check width/height to exclude image streams (that are included among video streams)
if (width != null && height != null) {
videoStreamCount++;
}
case MediaStreamType.audio:
audioStreamCount++;
case MediaStreamType.text:
textStreamCount++;
}
}
});
canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0;
final selectedVideo = await getSelectedStream(MediaStreamType.video);
if (selectedVideo != null) {
final streamIndex = selectedVideo.index;
final streamInfo = allStreams.firstWhereOrNull((stream) => stream[Keys.index] == streamIndex);
if (streamInfo != null) {
final num = streamInfo[Keys.sarNum] ?? 0;
final den = streamInfo[Keys.sarDen] ?? 0;
sarNotifier.value = (num != 0 ? num : 1) / (den != 0 ? den : 1);
}
}
}
@override
Future<MediaStreamSummary?> getSelectedStream(MediaStreamType type) async {
final currentIndex = await _instance.getSelectedTrack(type.code);
return currentIndex != -1 ? _streams.firstWhereOrNull((v) => v.index == currentIndex) : null;
}
// When a stream is selected, the video accelerates to catch up with it.
// The duration of this acceleration phase depends on the player `min-frames` parameter.
// Calling `seekTo` after stream de/selection is a workaround to:
// 1) prevent video stream acceleration to catch up with audio
// 2) apply timed text stream
@override
Future<void> selectStream(MediaStreamType type, MediaStreamSummary? selected) async {
final current = await getSelectedStream(type);
if (current == selected) return;
if (selected != null) {
final newIndex = selected.index;
if (newIndex != null) {
await _instance.selectTrack(newIndex);
}
} else if (current != null) {
await _instance.deselectTrack(current.index!);
}
if (type == MediaStreamType.text) {
_timedTextStreamController.add(null);
}
await seekTo(currentPosition);
}
}
extension ExtraIjkStatus on FijkState {

View file

@ -0,0 +1,21 @@
import 'package:aves_model/aves_model.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_ijk/aves_video_ijk.dart';
import 'package:fijkplayer/fijkplayer.dart';
class IjkVideoControllerFactory extends AvesVideoControllerFactory {
@override
void init() => FijkLog.setLevel(FijkLogLevel.Warn);
@override
AvesVideoController buildController(
AvesEntryBase entry, {
required PlaybackStateHandler playbackStateHandler,
required VideoSettings settings,
}) =>
IjkVideoController(
entry,
playbackStateHandler: playbackStateHandler,
settings: settings,
);
}

View file

@ -0,0 +1,23 @@
import 'package:aves_model/aves_model.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_ijk/aves_video_ijk.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart';
class IjkVideoMetadataFetcher extends AvesVideoMetadataFetcher {
@override
void init() => FijkLog.setLevel(FijkLogLevel.Warn);
@override
Future<Map> getMetadata(AvesEntryBase entry) async {
final player = FijkPlayer();
final info = await player.setDataSourceUntilPrepared(entry.uri).then((v) {
return player.getInfo();
}).catchError((error) {
debugPrint('failed to get video metadata for entry=$entry, error=$error');
return {};
});
await player.release();
return info;
}
}

View file

@ -64,10 +64,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
js:
dependency: transitive
description:

30
plugins/aves_video_mpv/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
#/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 796c8ef79279f9c774545b3771238c3098dbefab
channel: stable
project_type: package

View file

@ -0,0 +1 @@
include: ../../analysis_options.yaml

View file

@ -0,0 +1,4 @@
library aves_video_mpv;
export 'src/controller.dart';
export 'src/factory.dart';

View file

@ -0,0 +1,368 @@
import 'dart:async';
import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves_video/aves_video.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
class MpvVideoController extends AvesVideoController {
late Player _instance;
late VideoController _controller;
late VideoStatus _status;
final List<StreamSubscription> _subscriptions = [];
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
final StreamController<String?> _timedTextStreamController = StreamController.broadcast();
final AChangeNotifier _completedNotifier = AChangeNotifier();
@override
double get minSpeed => .25;
@override
double get maxSpeed => 4;
@override
final ValueNotifier<bool> canCaptureFrameNotifier = ValueNotifier(false);
@override
final ValueNotifier<bool> canMuteNotifier = ValueNotifier(true);
@override
final ValueNotifier<bool> canSetSpeedNotifier = ValueNotifier(true);
@override
final ValueNotifier<bool> canSelectStreamNotifier = ValueNotifier(false);
@override
final ValueNotifier<double> sarNotifier = ValueNotifier(1);
MpvVideoController(
super.entry, {
required super.playbackStateHandler,
required super.settings,
}) {
_status = VideoStatus.idle;
_statusStreamController.add(_status);
_instance = Player(
configuration: const PlayerConfiguration(
logLevel: MPVLogLevel.warn,
),
);
_initController();
_init();
// TODO TLAD listening
// canCaptureFrameNotifier.value = captureFrameEnabled && started;
_startListening();
}
@override
Future<void> dispose() async {
await super.dispose();
_stopListening();
_stopStreamFetchTimer();
await _statusStreamController.close();
await _timedTextStreamController.close();
await _instance.dispose();
}
void _startListening() {
_subscriptions.add(statusStream.listen((v) => _status = v));
_subscriptions.add(_instance.stream.completed.listen((v) {
if (v) {
_statusStreamController.add(VideoStatus.completed);
_completedNotifier.notify();
}
}));
_subscriptions.add(_instance.stream.playing.listen((v) {
if (status == VideoStatus.idle) return;
_statusStreamController.add(v ? VideoStatus.playing : VideoStatus.paused);
}));
_subscriptions.add(_instance.stream.log.listen((v) => debugPrint('libmpv log: $v')));
_subscriptions.add(_instance.stream.error.listen((v) => debugPrint('libmpv error: $v')));
_subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.enableVideoHardwareAccelerationKey).listen((_) => _initController()));
_subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.videoLoopModeKey).listen((_) => _applyLoop()));
}
void _stopListening() {
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
}
Future<void> _applyLoop() async {
final loopEnabled = settings.videoLoopMode.shouldLoop(entry.durationMillis);
await _instance.setPlaylistMode(loopEnabled ? PlaylistMode.single : PlaylistMode.none);
}
Future<void> _init({int startMillis = 0}) async {
final playing = _instance.state.playing;
await _applyLoop();
await _instance.open(Media(entry.uri), play: playing);
if (startMillis > 0) {
await seekTo(startMillis);
}
_fetchStreams();
_statusStreamController.add(_instance.state.playing ? VideoStatus.playing : VideoStatus.paused);
}
void _initController() {
_controller = VideoController(
_instance,
configuration: VideoControllerConfiguration(
enableHardwareAcceleration: settings.enableVideoHardwareAcceleration,
),
);
}
@override
void onVisualChanged() => _init(startMillis: currentPosition);
@override
Future<void> play() async {
await untilReady;
await _instance.play();
}
@override
Future<void> pause() => _instance.pause();
@override
Future<void> seekTo(int targetMillis) async {
if (!isReady) {
await untilReady;
// When the player gets ready, it can play from the beginning right away,
// but trying to seek then just plays from the start.
// There is no state or hook identifying readiness to seek on start,
// and `PlayerConfiguration.ready` hook is useless.
await Future.delayed(const Duration(milliseconds: 500));
}
await _instance.seek(Duration(milliseconds: targetMillis));
}
@override
Listenable get playCompletedListenable => _completedNotifier;
@override
VideoStatus get status => _status;
@override
Stream<VideoStatus> get statusStream => _statusStreamController.stream;
@override
Stream<double> get volumeStream => _instance.stream.volume;
@override
Stream<double> get speedStream => _instance.stream.rate;
@override
bool get isReady {
switch (_status) {
case VideoStatus.error:
case VideoStatus.idle:
case VideoStatus.initialized:
return false;
case VideoStatus.paused:
case VideoStatus.playing:
case VideoStatus.completed:
return true;
}
}
@override
int get duration => _instance.state.duration.inMilliseconds;
@override
int get currentPosition => _instance.state.position.inMilliseconds;
@override
Stream<int> get positionStream => _instance.stream.position.map((pos) => pos.inMilliseconds);
@override
Stream<String?> get timedTextStream => _timedTextStreamController.stream;
@override
bool get isMuted => _instance.state.volume == 0;
@override
Future<void> mute(bool muted) => _instance.setVolume(muted ? 0 : 100);
@override
double get speed => _instance.state.rate;
@override
set speed(double speed) => _instance.setRate(speed);
@override
Future<Uint8List> captureFrame() {
// TODO: implement captureFrame
throw UnimplementedError();
}
@override
Widget buildPlayerWidget(BuildContext context) {
// TODO TLAD handle SAR / DAR (media_kit Player.stream.width/height just gives raw video size, not rendered size)
return Video(
controller: _controller,
fit: BoxFit.cover,
alignment: Alignment.center,
controls: NoVideoControls,
wakelock: false,
);
}
// streams (aka tracks)
// `auto` and `no` are the first 2 tracks in the player state track lists
static const int fakeTrackCount = 2;
Tracks get _tracks => _instance.state.tracks;
List<VideoTrack> get _videoTracks => _tracks.video.skip(fakeTrackCount).toList();
List<AudioTrack> get _audioTracks => _tracks.audio.skip(fakeTrackCount).toList();
List<SubtitleTrack> get _subtitleTracks => _tracks.subtitle.skip(fakeTrackCount).toList();
@override
List<MediaStreamSummary> get streams {
return {
..._videoTracks.mapIndexed((i, v) => v.toAves(i)),
..._audioTracks.mapIndexed((i, v) => v.toAves(i)),
..._subtitleTracks.mapIndexed((i, v) => v.toAves(i)),
}.toList();
}
Timer? _streamFetchTimer;
void _stopStreamFetchTimer() {
_streamFetchTimer?.cancel();
_streamFetchTimer = null;
}
void _fetchStreams() {
_stopStreamFetchTimer();
_streamFetchTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
if (status != VideoStatus.error) {
if (_videoTracks.isEmpty && _audioTracks.isEmpty) return;
final videoStreamCount = _videoTracks.length;
final audioStreamCount = _audioTracks.length;
final textStreamCount = _subtitleTracks.length;
canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0;
}
_stopStreamFetchTimer();
});
}
@override
Future<MediaStreamSummary?> getSelectedStream(MediaStreamType type) async {
final track = _instance.state.track;
switch (type) {
case MediaStreamType.video:
final video = track.video;
if (video != VideoTrack.no()) {
final index = video == VideoTrack.auto() ? 0 : _videoTracks.indexOf(video);
return video.toAves(index);
}
case MediaStreamType.audio:
final audio = track.audio;
if (audio != AudioTrack.no()) {
final index = audio == AudioTrack.auto() ? 0 : _audioTracks.indexOf(audio);
return audio.toAves(index);
}
case MediaStreamType.text:
final subtitle = track.subtitle;
if (subtitle != SubtitleTrack.no()) {
final index = subtitle == SubtitleTrack.auto() ? 0 : _subtitleTracks.indexOf(subtitle);
return subtitle.toAves(index);
}
}
return null;
}
@override
Future<void> selectStream(MediaStreamType type, MediaStreamSummary? selected) async {
final current = await getSelectedStream(type);
if (current == selected) return;
if (selected != null) {
final newIndex = selected.index;
if (newIndex != null) {
// select track
switch (type) {
case MediaStreamType.video:
await _instance.setVideoTrack(_videoTracks[selected.index ?? 0]);
break;
case MediaStreamType.audio:
await _instance.setAudioTrack(_audioTracks[selected.index ?? 0]);
break;
case MediaStreamType.text:
await _instance.setSubtitleTrack(_subtitleTracks[selected.index ?? 0]);
break;
}
}
} else if (current != null) {
// deselect track
switch (type) {
case MediaStreamType.video:
await _instance.setVideoTrack(VideoTrack.no());
break;
case MediaStreamType.audio:
await _instance.setAudioTrack(AudioTrack.no());
break;
case MediaStreamType.text:
await _instance.setSubtitleTrack(SubtitleTrack.no());
break;
}
}
}
}
extension ExtraVideoTrack on VideoTrack {
MediaStreamSummary toAves(int index) {
return MediaStreamSummary(
type: MediaStreamType.video,
index: index,
codecName: null,
language: language,
title: title,
width: null,
height: null,
);
}
}
extension ExtraAudioTrack on AudioTrack {
MediaStreamSummary toAves(int index) {
return MediaStreamSummary(
type: MediaStreamType.audio,
index: index,
codecName: null,
language: language,
title: title,
width: null,
height: null,
);
}
}
extension ExtraSubtitleTrack on SubtitleTrack {
MediaStreamSummary toAves(int index) {
return MediaStreamSummary(
type: MediaStreamType.text,
index: index,
codecName: null,
language: language,
title: title,
width: null,
height: null,
);
}
}

View file

@ -0,0 +1,21 @@
import 'package:aves_model/aves_model.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_mpv/aves_video_mpv.dart';
import 'package:media_kit/media_kit.dart';
class MpvVideoControllerFactory extends AvesVideoControllerFactory {
@override
void init() => MediaKit.ensureInitialized();
@override
AvesVideoController buildController(
AvesEntryBase entry, {
required PlaybackStateHandler playbackStateHandler,
required VideoSettings settings,
}) =>
MpvVideoController(
entry,
playbackStateHandler: playbackStateHandler,
settings: settings,
);
}

View file

@ -0,0 +1,362 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
aves_model:
dependency: "direct main"
description:
path: "../aves_model"
relative: true
source: path
version: "0.0.1"
aves_utils:
dependency: "direct main"
description:
path: "../aves_utils"
relative: true
source: path
version: "0.0.1"
aves_video:
dependency: "direct main"
description:
path: "../aves_video"
relative: true
source: path
version: "0.0.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
collection:
dependency: "direct main"
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted
version: "1.17.1"
equatable:
dependency: transitive
description:
name: equatable
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
url: "https://pub.dev"
source: hosted
version: "2.0.5"
ffi:
dependency: transitive
description:
name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev"
source: hosted
version: "2.0.2"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
lints:
dependency: transitive
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.2.0"
media_kit:
dependency: "direct main"
description:
name: media_kit
sha256: "8c7d9417bed724a3fcaadd91c722fea042737cafb153aa1f1e6461a0fee683a3"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
media_kit_libs_android_video:
dependency: "direct main"
description:
name: media_kit_libs_android_video
sha256: "228c3b182831e194bb178d4d22a1839af812c917cb76fe87d6fdc9ea4328dc81"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
media_kit_native_event_loop:
dependency: "direct main"
description:
name: media_kit_native_event_loop
sha256: "5351f0c28124b5358756515d8619abad182cdefe967468d7fb5b274737cc2f59"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
media_kit_video:
dependency: "direct main"
description:
name: media_kit_video
sha256: d31a0eab80cafadccdedb663d8a127750e38b8c75c1aa83d8943f8119b88cf99
url: "https://pub.dev"
source: hosted
version: "1.0.2"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
safe_local_storage:
dependency: transitive
description:
name: safe_local_storage
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
url: "https://pub.dev"
source: hosted
version: "1.0.2"
screen_brightness:
dependency: transitive
description:
name: screen_brightness
sha256: "62fd61a64e68b32b98b840bad7d8b6822bbc40e63c2b569a5f85528484c86b41"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
screen_brightness_android:
dependency: transitive
description:
name: screen_brightness_android
sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf"
url: "https://pub.dev"
source: hosted
version: "0.1.0+2"
screen_brightness_ios:
dependency: transitive
description:
name: screen_brightness_ios
sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
screen_brightness_macos:
dependency: transitive
description:
name: screen_brightness_macos
sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd"
url: "https://pub.dev"
source: hosted
version: "0.1.0+1"
screen_brightness_platform_interface:
dependency: transitive
description:
name: screen_brightness_platform_interface
sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171
url: "https://pub.dev"
source: hosted
version: "0.1.0"
screen_brightness_windows:
dependency: transitive
description:
name: screen_brightness_windows
sha256: "80d90ecdc63fc0823f2ecb1be323471619287937e14210650d7b25ca181abd05"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
url: "https://pub.dev"
source: hosted
version: "1.0.0+1"
uri_parser:
dependency: transitive
description:
name: uri_parser
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
volume_controller:
dependency: transitive
description:
name: volume_controller
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
wakelock:
dependency: transitive
description:
name: wakelock
sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db"
url: "https://pub.dev"
source: hosted
version: "0.6.2"
wakelock_macos:
dependency: transitive
description:
name: wakelock_macos
sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_platform_interface:
dependency: transitive
description:
name: wakelock_platform_interface
sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_windows:
dependency: transitive
description:
name: wakelock_windows
sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
win32:
dependency: transitive
description:
name: win32
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
url: "https://pub.dev"
source: hosted
version: "3.1.4"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0"

View file

@ -0,0 +1,36 @@
name: aves_video_mpv
version: 0.0.1
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
aves_model:
path: ../aves_model
aves_video:
path: ../aves_video
aves_utils:
path: ../aves_utils
collection:
media_kit:
media_kit_video:
media_kit_native_event_loop:
media_kit_libs_android_video:
dev_dependencies:
flutter_lints:
#dependency_overrides:
# media_kit:
# path: ../../../media_kit/media_kit
# media_kit_video:
# path: ../../../media_kit/media_kit_video
# media_kit_native_event_loop:
# path: ../../../media_kit/media_kit_native_event_loop
# media_kit_libs_android_video:
# path: ../../../media_kit/media_kit_libs_android_video
flutter:

View file

@ -299,10 +299,10 @@ packages:
dependency: "direct main"
description:
name: dynamic_color
sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d
url: "https://pub.dev"
source: hosted
version: "1.6.5"
version: "1.6.6"
equatable:
dependency: "direct main"
description:
@ -345,7 +345,7 @@ packages:
source: hosted
version: "2.0.2"
fijkplayer:
dependency: "direct main"
dependency: transitive
description:
path: "."
ref: aves
@ -413,19 +413,18 @@ packages:
dependency: transitive
description:
name: flex_seed_scheme
sha256: e4168a6fc88a3e5bc3d6b7a748c6a6083eedc193d343ddc26bbad7fb1b258555
sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.0"
floating:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: b073419d48f099b5855816a7c6e04d397b1f1c37
url: "https://github.com/wrbl606/floating.git"
source: git
version: "2.0.0"
name: floating
sha256: d9d563089e34fbd714ffdcdd2df447ec41b40c9226dacae6b4f78847aef8b991
url: "https://pub.dev"
source: hosted
version: "2.0.1"
fluster:
dependency: "direct main"
description:
@ -472,10 +471,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
flutter_localization_nn:
dependency: "direct main"
description:
@ -502,10 +501,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0
sha256: "86b76dbf30496024d6c816bdc13b97de9449dce1f035a73ee7b4ab7f67eab70b"
url: "https://pub.dev"
source: hosted
version: "0.6.15"
version: "0.6.16"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -629,10 +628,10 @@ packages:
dependency: transitive
description:
name: google_maps_flutter_web
sha256: "5f58d7c491240b0074f455e70ce8d9b038f92472559e49e3b611d9f39b8d51a7"
sha256: "280170a2dcac3364317b5786f0d2e3c4128fdb795bc0d87ffe56226b0cf1f57d"
url: "https://pub.dev"
source: hosted
version: "0.5.0+1"
version: "0.5.1"
highlight:
dependency: transitive
description:
@ -653,10 +652,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.1.0"
http_multi_server:
dependency: transitive
description:
@ -991,42 +990,42 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "1b6b3e73f0bcbc856548bbdfb1c33084a401c4f143e220629a9055233d76c331"
sha256: "37fcc3c3182ac0bf8856f3e973e11c7bef5556d69f1a0d8fb908f51019c2912d"
url: "https://pub.dev"
source: hosted
version: "10.3.0"
version: "10.4.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "8f6a95ccbca13766882f95d32684d7c9bfe6c45650c32bedba948ef1c6a4ddf7"
sha256: "3b61f3da3b1c83bc3fb6a2b431e8dab01d0e5b45f6a3d9c7609770ec88b2a89e"
url: "https://pub.dev"
source: hosted
version: "10.2.3"
version: "10.3.0"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb"
sha256: "0d1f8007b17573ff1fbeae0f04b6c8e83e1d2f6c4fe8e8226d4d2456aa8ecffe"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
version: "9.1.2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11
sha256: "79b36d93a41a4aecfd0d635d77552f327cb84227c718ce5e68b5f7b85546fe7e"
url: "https://pub.dev"
source: hosted
version: "3.10.0"
version: "3.11.0+1"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev"
source: hosted
version: "0.1.2"
version: "0.1.3"
petitparser:
dependency: transitive
description:
@ -1039,10 +1038,10 @@ packages:
dependency: "direct main"
description:
name: pin_code_fields
sha256: c8652519d14688f3fe2a8288d86910a46aa0b9046d728f292d3bf6067c31b4c7
sha256: "4c0db7fbc889e622e7c71ea54b9ee624bb70c7365b532abea0271b17ea75b729"
url: "https://pub.dev"
source: hosted
version: "7.4.0"
version: "8.0.1"
platform:
dependency: transitive
description:
@ -1191,58 +1190,58 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "396f85b8afc6865182610c0a2fc470853d56499f75f7499e2a73a9f0539d23d0"
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.2.0"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
sha256: "0dc5c49ad8a05ed358b991b60c7b0ba1a14e16dae58af9b420d6b9e82dc024ab"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.3.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
shared_preferences_platform_interface:
dependency: "direct dev"
description:
name: shared_preferences_platform_interface
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
shelf:
dependency: transitive
description:
@ -1570,13 +1569,13 @@ packages:
source: hosted
version: "1.2.0"
win32:
dependency: transitive
dependency: "direct overridden"
description:
name: win32
sha256: "1414f27dd781737e51afa9711f2ac2ace6ab4498ee98e20863fa5505aa00c58c"
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
url: "https://pub.dev"
source: hosted
version: "5.0.4"
version: "5.0.5"
win32_registry:
dependency: transitive
description:

View file

@ -42,6 +42,8 @@ dependencies:
path: plugins/aves_video
aves_video_ijk:
path: plugins/aves_video_ijk
# aves_video_mpv:
# path: plugins/aves_video_mpv
aves_ui:
path: plugins/aves_ui
aves_utils:
@ -65,16 +67,8 @@ dependencies:
expansion_tile_card:
git:
url: https://github.com/deckerst/expansion_tile_card.git
fijkplayer:
git:
url: https://github.com/deckerst/fijkplayer.git
ref: aves
flex_color_picker:
floating:
git:
url: https://github.com/wrbl606/floating.git
# v2.0.0 is incompatible with AGP8
ref: main
fluster:
flutter_displaymode:
flutter_highlight:
@ -130,6 +124,19 @@ dev_dependencies:
shared_preferences_platform_interface:
test:
dependency_overrides:
# `media_kit v1.0.0` depends on `wakelock: ^0.6.2`
# which is incompatible with packages that moved on with Dart 3
win32: ^5.0.0
# media_kit:
# path: ../media_kit/media_kit
# media_kit_video:
# path: ../media_kit/media_kit_video
# media_kit_native_event_loop:
# path: ../media_kit/media_kit_native_event_loop
# media_kit_libs_android_video:
# path: ../media_kit/media_kit_libs_android_video
flutter:
assets:
- assets/