Explorar el Código

播放器与组件脱钩

gavin.chen hace 2 años
padre
commit
ee4089d62b

+ 4 - 7
lib/main.dart

@@ -1,12 +1,9 @@
-import 'package:fis_common/index.dart';
-import 'package:fis_vid/common/env.dart';
 import 'package:flutter/material.dart';
-import 'package:get/get.dart';
 import 'package:vid_player_demo/pages/single_image.dart/view.dart';
 import 'package:vid_player_demo/widgets/image_cache.dart';
 
-import 'pages/canvas_player/view.dart';
-import 'pages/image_player/view.dart';
+import 'pages/canvas_player/page.dart';
+import 'pages/image_player/page.dart';
 
 void main() {
   runApp(const MyApp());
@@ -25,8 +22,8 @@ class MyApp extends StatelessWidget {
         '/': (BuildContext context) =>
             const MyHomePage(title: 'Vid Player Demo Home Page'),
         '/single_image': (BuildContext context) => const SingleImageView(),
-        '/image_player': (BuildContext context) => const ImagePlayerView(),
-        '/canvas_player': (BuildContext context) => const CanvasPlayerView(),
+        '/image_player': (BuildContext context) => const ImagePlayerPage(),
+        '/canvas_player': (BuildContext context) => const CanvasPlayerPage(),
       },
       debugShowCheckedModeBanner: false,
     );

+ 15 - 0
lib/pages/canvas_player/page.dart

@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+import 'package:vid_player_demo/pages/canvas_player/view.dart';
+
+class CanvasPlayerPage extends StatelessWidget {
+  const CanvasPlayerPage({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("基于Canvas的播放器"),
+      ),
+      body: const CanvasPlayerView(),
+    );
+  }
+}

+ 23 - 28
lib/pages/canvas_player/view.dart

@@ -62,36 +62,31 @@ class _CanvasPlayerViewState extends State<CanvasPlayerView> {
 
   @override
   Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('基于Canvas的播放器'),
-      ),
-      body: Center(
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-          children: [
-            const ShowImageCache(),
-            ElevatedButton(
-                onPressed: () {
-                  clickPlay();
-                },
-                child: const Text('Play')),
-            ElevatedButton(
-                onPressed: () {
-                  clickPause();
-                },
-                child: const Text('Pause')),
-            SizedBox(
-              width: 600,
-              height: 600,
-              child: RepaintBoundary(
-                child: VidCanvasPlayer(
-                  _playerController as VidPlayerController,
-                ),
+    return Center(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        children: [
+          const ShowImageCache(),
+          ElevatedButton(
+              onPressed: () {
+                clickPlay();
+              },
+              child: const Text('Play')),
+          ElevatedButton(
+              onPressed: () {
+                clickPause();
+              },
+              child: const Text('Pause')),
+          SizedBox(
+            width: 600,
+            height: 600,
+            child: RepaintBoundary(
+              child: VidCanvasPlayer(
+                _playerController as VidPlayerController,
               ),
             ),
-          ],
-        ),
+          ),
+        ],
       ),
     );
   }

+ 30 - 3
lib/pages/canvas_player/widgets/canvas_player.dart

@@ -1,8 +1,10 @@
+import 'dart:collection';
 import 'dart:typed_data';
 import 'package:fis_measure/view/player/controller.dart';
 import 'package:fis_measure/view/player/enums.dart';
 import 'package:fis_measure/view/player/events.dart';
 import 'package:flutter/material.dart';
+import 'package:vid_player_demo/utils/utils.dart';
 import 'dart:ui' as ui;
 import 'vid_painter.dart';
 
@@ -27,9 +29,15 @@ class VidCanvasPlayer extends StatefulWidget {
 class _VidCanvasPlayerState extends State<VidCanvasPlayer> {
   Uint8List? frameBytes;
   ui.Image? image;
+  DateTime lastFrameTime = DateTime.now();
+  ListQueue<int> frames = ListQueue<int>()..addFirst(30); //预留一位防止分母为0
+  int maxFramesNum = 10;
+  int curFPS = 30;
+
   @override
   void initState() {
     widget.controller.eventHandler.addListener(onControllerEvent);
+    startFPSListener();
     super.initState();
   }
 
@@ -68,12 +76,23 @@ class _VidCanvasPlayerState extends State<VidCanvasPlayer> {
     // image?.dispose();
     image = await decodeImageFromList(bytes);
     setState(() {});
+    countCurFPS();
   }
 
   void updateFrame() {
     setState(() {});
   }
 
+  void countCurFPS() {
+    int fps = 1000 ~/ DateTime.now().difference(lastFrameTime).inMilliseconds;
+    lastFrameTime = DateTime.now();
+    frames.addFirst(fps);
+    while (frames.length > maxFramesNum) {
+      frames.removeLast();
+    }
+    curFPS = frames.reduce((a, b) => a + b) ~/ frames.length;
+  }
+
   @override
   Widget build(BuildContext context) {
     Widget? child;
@@ -99,10 +118,17 @@ class _VidCanvasPlayerState extends State<VidCanvasPlayer> {
     return buildBox(context, child);
   }
 
+  ///创建播放器容器,可以在此叠加信息
   Widget buildBox(BuildContext context, Widget child) {
-    return Container(
-      alignment: Alignment.center,
-      child: child,
+    return Stack(
+      children: [
+        Text(
+            "当前帧:${widget.controller.currentFrameIndex} \n估算平均帧率:$curFPS fps \n应用刷新率:$getScreenFPS fps"),
+        Container(
+          alignment: Alignment.center,
+          child: child,
+        ),
+      ],
     );
   }
 
@@ -122,6 +148,7 @@ class _VidCanvasPlayerState extends State<VidCanvasPlayer> {
   @override
   void dispose() {
     widget.controller.eventHandler.removeListener(onControllerEvent);
+    stopFPSListener();
     super.dispose();
   }
 }

+ 15 - 0
lib/pages/image_player/page.dart

@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+import 'view.dart';
+
+class ImagePlayerPage extends StatelessWidget {
+  const ImagePlayerPage({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("基于Image的播放器"),
+      ),
+      body: const ImagePlayerView(),
+    );
+  }
+}

+ 23 - 28
lib/pages/image_player/view.dart

@@ -62,36 +62,31 @@ class _ImagePlayerViewState extends State<ImagePlayerView> {
 
   @override
   Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('基于Image的播放器'),
-      ),
-      body: Center(
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-          children: [
-            const ShowImageCache(),
-            ElevatedButton(
-                onPressed: () {
-                  clickPlay();
-                },
-                child: const Text('Play')),
-            ElevatedButton(
-                onPressed: () {
-                  clickPause();
-                },
-                child: const Text('Pause')),
-            SizedBox(
-              width: 600,
-              height: 600,
-              child: RepaintBoundary(
-                child: VidImagePlayer(
-                  _playerController as VidPlayerController,
-                ),
+    return Center(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        children: [
+          const ShowImageCache(),
+          ElevatedButton(
+              onPressed: () {
+                clickPlay();
+              },
+              child: const Text('Play')),
+          ElevatedButton(
+              onPressed: () {
+                clickPause();
+              },
+              child: const Text('Pause')),
+          SizedBox(
+            width: 600,
+            height: 600,
+            child: RepaintBoundary(
+              child: VidImagePlayer(
+                _playerController as VidPlayerController,
               ),
             ),
-          ],
-        ),
+          ),
+        ],
       ),
     );
   }

+ 29 - 15
lib/pages/image_player/widgets/image_player.dart

@@ -1,3 +1,4 @@
+import 'dart:collection';
 import 'dart:typed_data';
 import 'package:fis_measure/view/frame_view/native/frame_view.dart';
 import 'package:fis_measure/view/player/controller.dart';
@@ -25,6 +26,10 @@ class VidImagePlayer extends StatefulWidget {
 
 class _VidImagePlayerState extends State<VidImagePlayer> {
   Uint8List? frameBytes;
+  DateTime lastFrameTime = DateTime.now();
+  ListQueue<int> frames = ListQueue<int>()..addFirst(30); //预留一位防止分母为0
+  int maxFramesNum = 10;
+  int curFPS = 30;
 
   @override
   void initState() {
@@ -58,6 +63,7 @@ class _VidImagePlayerState extends State<VidImagePlayer> {
   }
 
   void onFrameChanged(VidPlayerFrameIndexChangeEvent e) {
+    countCurFPS(); //计算视频帧率
     setState(() {
       frameBytes = e.bytes;
     });
@@ -67,41 +73,51 @@ class _VidImagePlayerState extends State<VidImagePlayer> {
     setState(() {});
   }
 
+  void countCurFPS() {
+    int fps = 1000 ~/ DateTime.now().difference(lastFrameTime).inMilliseconds;
+    lastFrameTime = DateTime.now();
+    frames.addFirst(fps);
+    while (frames.length > maxFramesNum) {
+      frames.removeLast();
+    }
+    curFPS = frames.reduce((a, b) => a + b) ~/ frames.length;
+  }
+
   @override
   Widget build(BuildContext context) {
     Widget? child;
     switch (widget.controller.status) {
       case VidPlayStatus.init:
-        child = Container(child: const Text("Loading"));
+        child = const Text("Loading");
         break;
       case VidPlayStatus.ready:
-        child = Container(child: const Text("Ready"));
+        child = const Text("Ready");
         break;
       case VidPlayStatus.loadFail:
-        child = Container(child: const Text("Load fail"));
+        child = const Text("Load fail");
         break;
       case VidPlayStatus.play:
       case VidPlayStatus.pause:
-        // return buildFrameView(context);
-        // child = Container(
-        //   child: Text(
-        //     widget.controller.currentFrameIndex.toString(),
-        //   ),
-        // );
         child = buildFrameView(context);
         break;
       case VidPlayStatus.stop:
       case VidPlayStatus.dispose:
-        child = Container(child: const Text("Closed"));
+        child = const Text("Closed");
         break;
     }
     return buildBox(context, child);
   }
 
+  ///创建播放器容器,可以在此叠加信息
   Widget buildBox(BuildContext context, Widget child) {
-    return Container(
-      alignment: Alignment.center,
-      child: child,
+    return Stack(
+      children: [
+        Text("当前帧:${widget.controller.currentFrameIndex}\n 估算平均帧率:$curFPS fps"),
+        Container(
+          alignment: Alignment.center,
+          child: child,
+        ),
+      ],
     );
   }
 
@@ -110,8 +126,6 @@ class _VidImagePlayerState extends State<VidImagePlayer> {
       final size = MediaQuery.of(context).size;
       return VidFrameView(
         frameBytes!,
-        // width: widget.width,
-        // height: widget.height,
         width: size.width,
         height: size.height,
       );

+ 58 - 0
lib/utils/utils.dart

@@ -1,3 +1,61 @@
+import 'dart:collection';
+import 'dart:ui';
+
+import 'package:flutter/scheduler.dart';
+
+/// 单位换算
 int byte2MB(int bytes) {
   return (bytes / 1024 / 1024).round();
 }
+
+// 需监听fps时注册
+void startFPSListener() {
+  SchedulerBinding.instance?.addTimingsCallback(_onReportTimings);
+}
+
+// 不需监听时移除
+void stopFPSListener() {
+  SchedulerBinding.instance?.removeTimingsCallback(_onReportTimings);
+}
+
+const maxframes = 100; // 100 帧足够了,对于 60 fps 来说
+final lastFrames = ListQueue<FrameTiming>(maxframes);
+const refreshRate = 60;
+const frameInterval =
+    Duration(microseconds: Duration.microsecondsPerSecond ~/ 60);
+
+void _onReportTimings(List<FrameTiming> timings) {
+  // 把 Queue 当作堆栈用
+  for (FrameTiming timing in timings) {
+    lastFrames.addFirst(timing);
+  }
+
+  // 只保留 maxframes
+  while (lastFrames.length > maxframes) {
+    lastFrames.removeLast();
+  }
+}
+
+double get getScreenFPS {
+  var lastFramesSet = <FrameTiming>[];
+  for (FrameTiming timing in lastFrames) {
+    if (lastFramesSet.isEmpty) {
+      lastFramesSet.add(timing);
+    } else {
+      var lastStart =
+          lastFramesSet.last.timestampInMicroseconds(FramePhase.buildStart);
+      if (lastStart - timing.timestampInMicroseconds(FramePhase.rasterFinish) >
+          (frameInterval.inMicroseconds * 2)) {
+        // in different set
+        break;
+      }
+      lastFramesSet.add(timing);
+    }
+  }
+  var framesCount = lastFramesSet.length;
+  var costCount = lastFramesSet.map((t) {
+    // 耗时超过 frameInterval 会导致丢帧
+    return (t.totalSpan.inMicroseconds ~/ frameInterval.inMicroseconds) + 1;
+  }).fold<int>(0, (a, b) => a + b);
+  return framesCount * refreshRate / costCount;
+}