Skip to content

Commit

Permalink
[1.0.0+1]😘Feat: 增加了无限播放视频/释放之前视频的功能
Browse files Browse the repository at this point in the history
  • Loading branch information
MaJialun committed Jul 5, 2021
1 parent ffe9266 commit 94f6e69
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 101 deletions.
249 changes: 249 additions & 0 deletions lib/controller/tikTokVideoListController.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import 'dart:async';

import 'package:flutter_tiktok/mock/video.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tiktok/other/pageView.dart';
import 'package:video_player/video_player.dart';

typedef LoadMoreVideo = Future<List<VPVideoController>> Function(
int index,
List<VPVideoController> list,
);

class TikTokVideoListController extends ChangeNotifier {
/// 构造方法
TikTokVideoListController({
this.preloadCount = 3,
this.disposeCount = 5,
});

/// 预加载多少个视频
final int preloadCount;

/// 超出多少个,就释放视频
final int disposeCount;

/// 提供视频的builder
LoadMoreVideo? _videoProvider;

loadIndex(int target) {
if (index.value == target) return;
// 播放当前的,暂停其他的
var oldIndex = index.value;
var newIndex = target;

// 暂停之前的视频
if (!(oldIndex == 0 && newIndex == 0)) {
playerOfIndex(oldIndex)?.controller.seekTo(Duration.zero);
playerOfIndex(oldIndex)?.pause();
playerOfIndex(oldIndex)?.controller.addListener(_didUpdateValue);
playerOfIndex(oldIndex)?.showPauseIcon.addListener(_didUpdateValue);
print('暂停$oldIndex');
}
// 开始播放当前的视频
playerOfIndex(newIndex)?.play();
playerOfIndex(newIndex)?.controller.addListener(_didUpdateValue);
playerOfIndex(newIndex)?.showPauseIcon.addListener(_didUpdateValue);
print('播放$newIndex');
// 处理预加载/释放内存
for (var i = 0; i < playerList.length; i++) {
// 需要释放[disposeCount]之前的视频
if (i < newIndex - disposeCount) {
print('释放$i');
playerOfIndex(i)?.controller.removeListener(_didUpdateValue);
playerOfIndex(i)?.showPauseIcon.removeListener(_didUpdateValue);
playerOfIndex(i)?.dispose();
} else {
// 需要预加载
if (i > newIndex && i < newIndex + preloadCount) {
print('预加载$i');
playerOfIndex(i)?.init();
}
}
}
// 快到最底部,添加更多视频
if (playerList.length - newIndex <= 2) {
_videoProvider?.call(newIndex, playerList).then(
(list) async {
playerList.addAll(list);
notifyListeners();
},
);
}

// 完成
index.value = target;
}

_didUpdateValue() {
notifyListeners();
}

/// 获取指定index的player
VPVideoController? playerOfIndex(int index) {
if (index < 0 || index > playerList.length - 1) {
return null;
}
return playerList[index];
}

/// 视频总数目
int get videoCount => playerList.length;

/// 初始化
init({
required TikTokPageController pageController,
required List<VPVideoController> initialList,
required LoadMoreVideo videoProvider,
}) async {
playerList.addAll(initialList);
_videoProvider = videoProvider;
pageController.addListener(() {
var p = pageController.page!;
if (p % 1 == 0) {
loadIndex(p ~/ 1);
}
});
playerList.first.play();
notifyListeners();
}

/// 目前的视频序号
ValueNotifier<int> index = ValueNotifier<int>(0);

/// 视频列表
List<VPVideoController> playerList = [];

///
VPVideoController get currentPlayer => playerList[index.value];

/// 销毁全部
void dispose() {
// 销毁全部
for (var player in playerList) {
player.dispose();
}
playerList = [];
super.dispose();
}
}

typedef ControllerSetter<T> = Future<void> Function(T controller);
typedef ControllerBuilder<T> = T Function();

/// 抽象类,作为视频控制器必须实现这些方法
abstract class TikTokVideoController<T> {
/// 获取当前的控制器实例
T? get controller;

/// 是否显示暂停按钮
ValueNotifier<bool> get showPauseIcon;

/// 加载视频,在init后,应当开始下载视频内容
Future<void> init({ControllerSetter<T>? afterInit});

/// 视频销毁,在dispose后,应当释放任何内存资源
Future<void> dispose();

/// 播放
Future<void> play();

/// 暂停
Future<void> pause({bool showPauseIcon: false});
}

class VPVideoController extends TikTokVideoController<VideoPlayerController> {
VideoPlayerController? _controller;
ValueNotifier<bool> _showPauseIcon = ValueNotifier<bool>(false);

final UserVideo? videoInfo;

final ControllerBuilder<VideoPlayerController> _builder;
final ControllerSetter<VideoPlayerController>? _afterInit;
VPVideoController({
this.videoInfo,
required ControllerBuilder<VideoPlayerController> builder,
ControllerSetter<VideoPlayerController>? afterInit,
}) : this._builder = builder,
this._afterInit = afterInit;

@override
VideoPlayerController get controller {
if (_controller == null) {
_controller = _builder.call();
}
return _controller!;
}

/// 阻止在init的时候dispose,或者在dispose前init
List<Future> _actLocks = [];

bool get isDispose => _disposeLock != null;
bool get prepared => _prepared;
bool _prepared = false;

Completer<void>? _disposeLock;

@override
Future<void> dispose() async {
if (!prepared) return;
await Future.wait(_actLocks);
_actLocks.clear();
var completer = Completer<void>();
_actLocks.add(completer.future);
_prepared = false;
await this.controller.dispose();
_controller = null;
_disposeLock = Completer<void>();
completer.complete();
}

@override
Future<void> init({
ControllerSetter<VideoPlayerController>? afterInit,
}) async {
if (prepared) return;
await Future.wait(_actLocks);
_actLocks.clear();
var completer = Completer<void>();
_actLocks.add(completer.future);
await this.controller.initialize();
afterInit ??= this._afterInit;
await afterInit?.call(this.controller);
_prepared = true;
completer.complete();
if (_disposeLock != null) {
_disposeLock?.complete();
_disposeLock = null;
}
}

@override
Future<void> pause({bool showPauseIcon: false}) async {
await Future.wait(_actLocks);
if (!prepared) await init();
_actLocks.clear();
if (_disposeLock != null) {
await _disposeLock?.future;
}
await this.controller.pause();
if (showPauseIcon) {
_showPauseIcon.value = true;
}
}

@override
Future<void> play() async {
await Future.wait(_actLocks);
if (!prepared) await init();
_actLocks.clear();
if (_disposeLock != null) {
await _disposeLock?.future;
}
await this.controller.play();
_showPauseIcon.value = false;
}

@override
ValueNotifier<bool> get showPauseIcon => _showPauseIcon;
}
39 changes: 28 additions & 11 deletions lib/pages/homePage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:flutter_tiktok/views/tikTokHeader.dart';
import 'package:flutter_tiktok/views/tikTokScaffold.dart';
import 'package:flutter_tiktok/views/tikTokVideo.dart';
import 'package:flutter_tiktok/views/tikTokVideoButtonColumn.dart';
import 'package:flutter_tiktok/views/tikTokVideoPlayer.dart';
import 'package:flutter_tiktok/controller/tikTokVideoListController.dart';
import 'package:flutter_tiktok/views/tiktokTabBar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Expand All @@ -33,7 +33,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {

TikTokPageController _pageController = TikTokPageController();

VideoListController _videoListController = VideoListController();
TikTokVideoListController _videoListController = TikTokVideoListController();

/// 记录点赞
Map<int, bool> favoriteMap = {};
Expand All @@ -59,8 +59,25 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
videoDataList = UserVideo.fetchVideo();
WidgetsBinding.instance!.addObserver(this);
_videoListController.init(
_pageController,
videoDataList,
pageController: _pageController,
initialList: videoDataList
.map(
(e) => VPVideoController(
videoInfo: e,
builder: () => VideoPlayerController.network(e.url),
),
)
.toList(),
videoProvider: (int index, List<VPVideoController> list) async {
return videoDataList
.map(
(e) => VPVideoController(
videoInfo: e,
builder: () => VideoPlayerController.network(e.url),
),
)
.toList();
},
);
_videoListController.addListener(() {
setState(() {});
Expand Down Expand Up @@ -165,9 +182,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
itemCount: _videoListController.videoCount,
itemBuilder: (context, i) {
// 拼一个视频组件出来
var data = videoDataList[i];
bool isF = SafeMap(favoriteMap)[i].boolean ?? false;
var player = _videoListController.playerOfIndex(i);
var player = _videoListController.playerOfIndex(i)!;
var data = player.videoInfo!;
// 右侧按钮列
Widget buttons = TikTokButtonColumn(
isFavorite: isF,
Expand All @@ -193,14 +210,14 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
// video
Widget currentVideo = Center(
child: AspectRatio(
aspectRatio: player.value.aspectRatio,
child: VideoPlayer(player),
aspectRatio: player.controller.value.aspectRatio,
child: VideoPlayer(player.controller),
),
);

currentVideo = TikTokVideoPage(
// TODO: 手势播放与自然播放都会产生暂停按钮状态变化,待处理
hidePauseIcon: true,
// 手势播放与自然播放都会产生暂停按钮状态变化,待处理
hidePauseIcon: !player.showPauseIcon.value,
aspectRatio: 9 / 16.0,
key: Key(data.url + '$i'),
tag: data.url,
Expand All @@ -210,7 +227,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
bottomPadding: hasBottomPadding ? 16.0 : 50.0,
),
onSingleTap: () async {
if (player.value.isPlaying) {
if (player.controller.value.isPlaying) {
await player.pause();
} else {
await player.play();
Expand Down
1 change: 0 additions & 1 deletion lib/views/tikTokVideo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ class TikTokVideoPage extends StatelessWidget {
width: double.infinity,
),
),
// TODO:播放状态问题
hidePauseIcon
? Container()
: Container(
Expand Down
Loading

0 comments on commit 94f6e69

Please sign in to comment.