example/lib/preload/ali_player_preload.dart (142 lines of code) (raw):

// Copyright © 2025 Alibaba Cloud. All rights reserved. // // Author: keria // Date: 2025/2/28 // Brief: AliPlayer Preload import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_aliplayer/flutter_aliplayer_medialoader.dart'; import 'package:aliplayer_widget/aliplayer_widget_lib.dart'; import 'package:aliplayer_widget_example/model/video_info.dart'; import 'package:path_provider/path_provider.dart'; import 'ali_sliding_window.dart'; /// A class that manages video and cover preloading logic using the sliding window concept. /// By using the idea of sliding windows, handle the logic of MediaLoader preloading to ensure the effect of full screen second opening /// /// 使用滑动窗口概念管理视频和封面预加载逻辑的类。 class AliPlayerPreload { /// The tag for logging purposes. /// /// 日志标签 static const String _logTag = 'AliPlayerPreload'; /// Preload buffer duration in milliseconds. /// /// 预加载缓冲时长(毫秒) static const int _preloadBufferDuration = 3 * 1000; /// Media preload window configuration. /// /// 媒体预加载窗口配置 static const List<int> _mediaPreloadWindows = [-1, 1, 2]; /// Cover preload left window size. /// /// 封面预加载左侧窗口大小 static const int _coverPreloadLeftWindowSize = 3; /// Cover preload right window size. /// /// 封面预加载右侧窗口大小 static const int _coverPreloadRightWindowSize = 10; /// Video preloader instance. /// /// 视频预加载器实例 late final AliSlidingWindow<String> _videoPreloader; /// Cover preloader instance. /// /// 封面预加载器实例 late final AliSlidingWindow<String>? _coverPreloader; /// Build context. /// /// Flutter 的 BuildContext 上下文 final BuildContext _context; /// Whether to enable cover URL strategy. /// /// 是否启用封面 URL 策略 final bool _isCoverUrlStrategyEnabled; /// Media loader instance. /// /// 媒体加载器实例 final FlutterAliPlayerMediaLoader _mediaLoader = FlutterAliPlayerMediaLoader(); /// Constructor for AliPlayerPreload. /// /// 构造函数,初始化 AliPlayerPreload 实例 AliPlayerPreload({ required BuildContext context, required bool enableCoverUrlStrategy, }) : _context = context, _isCoverUrlStrategyEnabled = enableCoverUrlStrategy { _log("[init]"); _initializeSlidingWindows(); _initMediaLoader(); } /// Initialize sliding windows after the instance is created. /// /// 初始化滑动窗口实例 void _initializeSlidingWindows() { // Initialize the video preloader. _videoPreloader = AliSlidingWindow.withCustomItems( _mediaPreloadWindows, _preloadMedia, _cancelPreloadMedia, isValid: _isValidUrl, extra: 'VIDEO', ); // Initialize the cover preloader if enabled. _coverPreloader = _isCoverUrlStrategyEnabled ? AliSlidingWindow.withWindowSize( _coverPreloadLeftWindowSize, _coverPreloadRightWindowSize, _preloadImage, null, isValid: _isValidUrl, extra: 'COVER', ) : null; } /// Initialize media loader. /// /// 初始化媒体加载器 Future<void> _initMediaLoader() async { // Setup global configuration. final Directory appDocumentsDir = await getApplicationDocumentsDirectory(); AliPlayerWidgetGlobalSetting.setupMediaLoaderConfig(appDocumentsDir.path); // Set the listener for media loader events. _mediaLoader.setOnLoadStatusListener( (String url) { // OnCompletion block _log("[cbk][video][completion], $url", isError: false); }, (String url) { // OnCancel block _log("[cbk][video][cancel], $url", isError: false); }, (String url, int code, String msg) { // OnError block }, (String url, String code, String msg) { // OnErrorV2 block _log("[cbk][video][errorV2], $url, $code, $msg", isError: true); }, ); } /// Release resources and clean up. /// /// 释放资源并清理 void destroy() { _log("[api][destroy]"); _videoPreloader.release(); _coverPreloader?.release(); _releaseMediaLoader(); } /// Release media loader resources. /// /// 释放媒体加载器资源 void _releaseMediaLoader() { // Clear the listener to avoid unnecessary callbacks. _mediaLoader.setOnLoadStatusListener(null, null, null, null); } /// Set video items (replace existing items). /// /// 设置视频项(替换现有项) void setItems(List<VideoInfo> items) { _log("[api][set], ${items.length}"); _updateItems(items, overwrite: true); } /// Add video items (append to existing items). /// /// 添加视频项(追加到现有项) void addItems(List<VideoInfo> items) { _log("[api][add], ${items.length}"); _updateItems(items, overwrite: false); } /// Move to a specific position in the item list. /// /// 移动到指定位置 void moveTo(int position) { _log("[api][moveTo], $position"); _videoPreloader.moveTo(position); _coverPreloader?.moveTo(position); } /// Update video and cover items. /// /// 更新视频和封面项 void _updateItems( List<VideoInfo> items, { required bool overwrite, }) { if (items.isEmpty) return; final videoUrls = <String>{}; final coverUrls = <String>{}; for (final item in items) { if (_isValidUrl(item.videoUrl)) videoUrls.add(item.videoUrl); if (_isValidUrl(item.coverUrl)) coverUrls.add(item.coverUrl); } if (overwrite) { _videoPreloader.setItems(videoUrls.toList()); _coverPreloader?.setItems(coverUrls.toList()); } else { _videoPreloader.addItems(videoUrls.toList()); _coverPreloader?.addItems(coverUrls.toList()); } } /// Validate if a URL is valid. /// /// 验证 URL 是否有效 static bool _isValidUrl(String? url) { if (url == null || url.isEmpty) return false; final uri = Uri.tryParse(url); return uri != null && uri.isAbsolute; } /// Preload media using MediaLoader. /// /// 使用 MediaLoader 预加载媒体资源 void _preloadMedia(String url) { Future.microtask(() async { try { _log("[preload][video][load], $url"); _mediaLoader.load(url, _preloadBufferDuration); } catch (e) { _log("[preload][video][load][error], $e", isError: true); } }); } /// Cancel media preloading. /// /// 取消媒体预加载 void _cancelPreloadMedia(String url) { Future.microtask(() async { try { _log("[preload][video][cancel], $url"); _mediaLoader.cancel(url); } catch (e) { _log("[preload][video][cancel][error], $e", isError: true); } }); } /// Preload image using precacheImage. /// /// 使用 precacheImage 预加载图片 void _preloadImage(String url) { Future.microtask(() async { _log("[preload][image], $url"); await precacheImage(NetworkImage(url), _context, onError: (_, __) { _log("[cbk][image][error], $url", isError: true); }); }); } /// Log information with a consistent format. /// /// 使用统一格式记录日志信息。 static void _log(String message, {bool isError = false}) { debugPrint("[$_logTag][${isError ? 'E' : 'I'}] $message"); } }