Browse Source

implement native player

melon.yin 2 years ago
parent
commit
fab782d839

+ 22 - 2
android/build.gradle

@@ -1,8 +1,18 @@
 buildscript {
     ext.kotlin_version = '1.6.10'
     repositories {
-        google()
+        mavenLocal() // 查找本地maven仓库
+        maven { url 'https://maven.aliyun.com/repository/central' } //central
+        maven { url 'https://maven.aliyun.com/repository/public' } //jcenter
+        maven { url 'https://maven.aliyun.com/repository/google' } //google
+        maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } //gradle-plugin
+        maven { url 'https://maven.aliyun.com/repository/spring' } //spring
+        maven { url 'https://maven.aliyun.com/repository/spring-plugin' } //spring-plugin
+        maven { url 'https://maven.aliyun.com/repository/grails-core' } //grails-core
+        maven { url 'https://maven.aliyun.com/repository/apache-snapshots' } //apache snapshots
+        
         mavenCentral()
+        google()
     }
 
     dependencies {
@@ -13,8 +23,18 @@ buildscript {
 
 allprojects {
     repositories {
-        google()
+        mavenLocal() // 查找本地maven仓库
+        maven { url 'https://maven.aliyun.com/repository/central' } //central
+        maven { url 'https://maven.aliyun.com/repository/public' } //jcenter
+        maven { url 'https://maven.aliyun.com/repository/google' } //google
+        maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } //gradle-plugin
+        maven { url 'https://maven.aliyun.com/repository/spring' } //spring
+        maven { url 'https://maven.aliyun.com/repository/spring-plugin' } //spring-plugin
+        maven { url 'https://maven.aliyun.com/repository/grails-core' } //grails-core
+        maven { url 'https://maven.aliyun.com/repository/apache-snapshots' } //apache snapshots
+        
         mavenCentral()
+        google()
     }
 }
 

+ 67 - 20
lib/data_host/native/data_host.dart

@@ -1,4 +1,13 @@
+import 'dart:io';
+import 'dart:isolate';
+import 'dart:typed_data';
+
 import 'package:fis_lib_measure/data_host/interface/data_host.dart';
+import 'package:fis_lib_measure/data_host/vid_download.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter/rendering.dart';
+import 'package:image/image.dart' as img;
+import 'package:path_provider/path_provider.dart';
 import 'package:vid/us/vid_us_image.dart';
 import 'package:fis_lib_measure/processors/base.dart';
 import 'package:vid/us/vid_us_image_data.dart';
@@ -6,40 +15,78 @@ import 'package:vid/us/vid_us_probe.dart';
 
 class VidDataHost implements VidDataHostInterface {
   VidDataHost(this.url);
+  static const _kStorageDir = 'ExamRecords';
+
+  VidUsImageData? _data;
 
   @override
   final String url;
 
   @override
-  Future<VidUsImage> getFrame<TProcessor extends VidFrameProcessor>(int index,
-      {List<TProcessor>? processors}) {
-    // TODO: implement getFrame
-    throw UnimplementedError();
-  }
+  VidUsProbe get probe => _data!.probe;
 
   @override
-  Future<VidDataHostLoadInfo?> load() {
-    // TODO: implement load
-    throw UnimplementedError();
-  }
+  int get frameCount => _data!.imageCount;
 
   @override
-  Future<void> release() {
-    // TODO: implement release
-    throw UnimplementedError();
-  }
+  Future<VidUsImageData?> getData() async => _data;
 
   @override
-  Future<VidUsImageData?> getData() {
-    // TODO: implement getData
-    throw UnimplementedError();
+  Future<VidDataHostLoadInfo?> load() async {
+    final vidFileBuffer = await _loadVidFile(url);
+    if (vidFileBuffer == null) return null;
+
+    _data = VidUsImageData(vidFileBuffer);
+    return VidDataHostLoadInfo(probe);
   }
 
   @override
-  // TODO: implement probe
-  VidUsProbe get probe => throw UnimplementedError();
+  Future<VidUsImage> getFrame<TProcessor extends VidFrameProcessor>(
+    int index, {
+    List<TProcessor>? processors,
+  }) async {
+    if (_data == null) {
+      throw Exception(
+          "[VidDataHost] getFrame: must call load first and data is not null.");
+    }
+
+    final frame = _data!.getImage(index);
+    return VidFrameProcessor.processFrame(frame, processors);
+  }
 
   @override
-  // TODO: implement frameCount
-  int get frameCount => throw UnimplementedError();
+  Future<void> release() async {
+    _data = null;
+  }
+
+  Future<Uint8List?> _loadVidFile(String url) async {
+    try {
+      final name = url.split('/').last.split('.').first;
+      final fileName = '$name.dat';
+      final cachePath = await _getStoragePath(fileName);
+      var file = File(cachePath);
+      if (await file.exists()) {
+        final buffer = await file.readAsBytes();
+        return buffer;
+      } else {
+        final buffer = await VidFileDownloader.download(url);
+        if (buffer != null) {
+          file = await file.create(recursive: true);
+          file.writeAsBytes(buffer);
+        }
+        return buffer;
+      }
+    } catch (e) {
+      return null;
+    }
+  }
+
+  Future<String> _getStoragePath(String fileName) async {
+    Directory? dir;
+    if (Platform.isAndroid) {
+      dir = await getExternalStorageDirectory();
+    }
+    dir ??= await getApplicationDocumentsDirectory();
+    return "${dir.path}/$_kStorageDir/$fileName";
+  }
 }

+ 2 - 0
lib/data_host/web/data_host.dart

@@ -9,6 +9,8 @@ import 'package:fis_lib_measure/processors/base.dart';
 import 'package:fis_jsonrpc/rpc.dart';
 import 'package:fis_lib_measure/processors/brightness.dart';
 import 'package:fis_lib_measure/processors/contrast.dart';
+import 'package:flutter/painting.dart';
+import 'package:image/image.dart' as img;
 import 'package:vid/us/vid_us_image.dart';
 import 'package:vid/us/vid_us_image_data.dart';
 import 'package:vid/us/vid_us_probe.dart';

+ 6 - 1
lib/main.dart

@@ -1,8 +1,11 @@
+import 'dart:io';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 
 import 'img_demo.dart';
 import 'player/player.dart';
 import 'vid.dart';
+import 'vid_player_mtest.dart';
 import 'vid_player_test.dart';
 
 void main() {
@@ -124,9 +127,11 @@ class _MyHomePageState extends State<MyHomePage> {
               onPressed: () {
                 const url =
                     "http://192.168.6.117:9001/Flyinsono-BJ-1300984704.VCS.AP-BeiJing/default.VID";
+                // final isMobile = Platform.isAndroid || Platform.isIOS;
                 Navigator.of(context).push(
                   MaterialPageRoute(
-                    builder: (_) => VidPlayerPage(url),
+                    builder: (_) =>
+                        !kIsWeb ? VidPlayerMobilePage(url) : VidPlayerPage(url),
                   ),
                 );
               },

+ 11 - 1
lib/player/control_board/control_board.dart

@@ -1,3 +1,4 @@
+import 'package:fis_lib_measure/processors/index.dart';
 import 'package:flutter/material.dart';
 
 import '../controller.dart';
@@ -7,6 +8,7 @@ part 'play_btn.dart';
 part 'prev_btn.dart';
 part 'next_btn.dart';
 part 'progress_bar.dart';
+part 'tone_bar.dart';
 
 class VidPlayerControlBoard extends StatelessWidget {
   const VidPlayerControlBoard(this.controller, {Key? key}) : super(key: key);
@@ -22,6 +24,8 @@ class VidPlayerControlBoard extends StatelessWidget {
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
+            _BrightnessToneBar(),
+            _ContrastToneBar(),
             _buildPlayStatusBar(),
           ],
         ),
@@ -57,7 +61,13 @@ class _SharedWidget extends InheritedWidget {
   final VidPlayerController controller;
 
   static _SharedWidget? of(BuildContext context) {
-    return context.dependOnInheritedWidgetOfExactType<_SharedWidget>();
+    try {
+      final data = context.dependOnInheritedWidgetOfExactType<_SharedWidget>();
+      return data;
+    } catch (e) {
+      //
+    }
+    return null;
   }
 
   @override

+ 2 - 2
lib/player/control_board/play_btn.dart

@@ -53,8 +53,8 @@ class _PlayButtonState extends State<_PlayButton> {
 
   @override
   void dispose() {
-    _SharedWidget.of(context)!
-        .controller
+    _SharedWidget.of(context)
+        ?.controller
         .eventHandler
         .removeListener(onControllerEvent);
     super.dispose();

+ 266 - 0
lib/player/control_board/tone_bar.dart

@@ -0,0 +1,266 @@
+part of 'control_board.dart';
+
+class _ContrastToneBar extends StatefulWidget {
+  @override
+  State<StatefulWidget> createState() => _ContrastToneBarState();
+}
+
+class _ContrastToneBarState extends State<_ContrastToneBar> {
+  @override
+  void initState() {
+    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
+      if (mounted) {
+        _SharedWidget.of(context)!
+            .controller
+            .eventHandler
+            .addListener(onControllerEvent);
+      }
+    });
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final controller = _SharedWidget.of(context)!.controller;
+    final processor = controller.getProcessor<VidContrastProcessor>();
+    return _ToneBar(
+      max: 99,
+      min: -99,
+      value: processor?.contrast.toDouble() ?? 0.0,
+      icon: const Icon(Icons.brightness_medium),
+      onChange: (v) {
+        controller.setContrast(v.toInt());
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    _SharedWidget.of(context)
+        ?.controller
+        .eventHandler
+        .removeListener(onControllerEvent);
+    super.dispose();
+  }
+
+  void onControllerEvent(Object sender, VidPlayerEvent e) {
+    if (e is VidPlayerContrastChangeEvent) {
+      onPlayStatusChanged(e);
+    }
+  }
+
+  void onPlayStatusChanged(VidPlayerContrastChangeEvent e) {
+    setState(() {});
+  }
+}
+
+class _BrightnessToneBar extends StatefulWidget {
+  @override
+  State<StatefulWidget> createState() => _BrightnessToneBarState();
+}
+
+class _BrightnessToneBarState extends State<_BrightnessToneBar> {
+  @override
+  void initState() {
+    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
+      if (mounted) {
+        _SharedWidget.of(context)!
+            .controller
+            .eventHandler
+            .addListener(onControllerEvent);
+      }
+    });
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final controller = _SharedWidget.of(context)!.controller;
+    final processor = controller.getProcessor<VidBrightnessProcessor>();
+    return _ToneBar(
+      max: 255,
+      min: -255,
+      value: processor?.brightness.toDouble() ?? 0.0,
+      icon: const Icon(Icons.wb_sunny_sharp),
+      onChange: (v) {
+        controller.setBrightness(v.toInt());
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    _SharedWidget.of(context)
+        ?.controller
+        .eventHandler
+        .removeListener(onControllerEvent);
+    super.dispose();
+  }
+
+  void onControllerEvent(Object sender, VidPlayerEvent e) {
+    if (e is VidPlayerBrightnessChangeEvent) {
+      onPlayStatusChanged(e);
+    }
+  }
+
+  void onPlayStatusChanged(VidPlayerBrightnessChangeEvent e) {
+    setState(() {});
+  }
+}
+
+class _ToneBar extends StatefulWidget {
+  const _ToneBar({
+    Key? key,
+    required this.icon,
+    required this.min,
+    required this.max,
+    required this.value,
+    this.onChange,
+  }) : super(key: key);
+
+  final Widget icon;
+  final ValueChanged<double>? onChange;
+  final double min;
+  final double max;
+  final double value;
+
+  @override
+  State<StatefulWidget> createState() => _ToneBarState();
+}
+
+class _ToneBarState extends State<_ToneBar> {
+  static const _kIconSize = 20.0;
+  static const splashRadius = _kIconSize / 2 + 3;
+
+  late double _value;
+
+  @override
+  void initState() {
+    syncProps();
+    super.initState();
+  }
+
+  @override
+  void didUpdateWidget(covariant _ToneBar oldWidget) {
+    syncProps();
+    super.didUpdateWidget(oldWidget);
+  }
+
+  void syncProps() {
+    _value = widget.value;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        const SizedBox(width: 8),
+        widget.icon,
+        const SizedBox(width: 8),
+        buildReduceIcon(),
+        buildSlider(context),
+        buildIncreaseIcon(),
+        const SizedBox(width: 8),
+        Text('${_value.toInt()}'),
+      ],
+    );
+  }
+
+  Widget buildReduceIcon() {
+    return IconButton(
+      constraints: const BoxConstraints(
+        minHeight: _kIconSize,
+        minWidth: _kIconSize,
+      ),
+      splashRadius: splashRadius,
+      padding: EdgeInsets.zero,
+      onPressed: () {
+        updateValue(_value - 1);
+      },
+      icon: const Icon(Icons.remove_circle_outline, size: _kIconSize),
+    );
+  }
+
+  Widget buildIncreaseIcon() {
+    return IconButton(
+      constraints: const BoxConstraints(
+        minHeight: _kIconSize,
+        minWidth: _kIconSize,
+      ),
+      splashRadius: splashRadius,
+      padding: EdgeInsets.zero,
+      onPressed: () {
+        updateValue(_value + 1);
+      },
+      icon: const Icon(Icons.add_circle_outline, size: _kIconSize),
+    );
+  }
+
+  Widget buildSlider(BuildContext context) {
+    final trackColor = Theme.of(context).primaryColor;
+    return SliderTheme(
+      data: SliderThemeData(
+        trackHeight: 4,
+        activeTrackColor: trackColor,
+        inactiveTrackColor: trackColor,
+        thumbColor: Colors.grey[300],
+        thumbShape: const RoundSliderThumbShape(
+          enabledThumbRadius: 6,
+          elevation: 3.0,
+        ),
+        overlayShape: SliderComponentShape.noOverlay,
+        trackShape: const _FullWidthRectangularSliderTrackShape(
+          padding: EdgeInsets.symmetric(horizontal: 4),
+        ),
+      ),
+      child: Slider(
+        max: widget.max,
+        min: widget.min,
+        value: _value,
+        label: _value.toInt().toString(),
+        divisions: (widget.max - widget.min).toInt(),
+        onChanged: (v) {
+          updateValue(v);
+        },
+      ),
+    );
+  }
+
+  void updateValue(double value) {
+    setState(() {
+      _value = value;
+    });
+    widget.onChange?.call(value);
+  }
+}
+
+class _FullWidthRectangularSliderTrackShape
+    extends RectangularSliderTrackShape {
+  const _FullWidthRectangularSliderTrackShape({this.padding});
+
+  final EdgeInsets? padding;
+
+  @override
+  Rect getPreferredRect({
+    required RenderBox parentBox,
+    Offset offset = Offset.zero,
+    required SliderThemeData sliderTheme,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+  }) {
+    double trackHeight = sliderTheme.trackHeight ?? 2;
+    double trackLeft = offset.dx;
+    double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
+    // 让轨道宽度等于 Slider 宽度
+    double trackWidth = parentBox.size.width;
+    if (padding != null) {
+      trackHeight -= padding!.bottom;
+      trackHeight -= padding!.top;
+      trackWidth -= padding!.right;
+      trackWidth -= padding!.left;
+      trackTop += padding!.top;
+      trackLeft += padding!.left;
+    }
+    return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
+  }
+}

+ 44 - 11
lib/player/controller.dart

@@ -1,6 +1,7 @@
 import 'dart:async';
 
 import 'package:fis_common/event/event_type.dart';
+import 'package:fis_common/func/func_proxy.dart';
 import 'package:fis_lib_measure/data_host/data_host.dart';
 import 'package:fis_lib_measure/processors/index.dart';
 import 'package:flutter/foundation.dart';
@@ -129,7 +130,7 @@ class VidPlayerController extends ChangeNotifier {
     if (index < 0 || index >= totalFramesCount) return false;
 
     _frameIndex = index;
-    await _updateFrame();
+    _updateFrame();
     return true;
   }
 
@@ -137,28 +138,59 @@ class VidPlayerController extends ChangeNotifier {
   ///
   /// [value] brightness value
   void setBrightness(int value) {
+    final processor =
+        _processors[VidBrightnessProcessor] as VidBrightnessProcessor?;
+    final originVal = processor?.brightness;
+    if (originVal == value) return;
+
     if (value == 0) {
       _processors.remove(VidBrightnessProcessor);
     } else {
       _processors[VidBrightnessProcessor] = VidBrightnessProcessor(value);
     }
     eventHandler.emit(this, VidPlayerBrightnessChangeEvent(value));
+    _updateFrame();
   }
 
   /// Set frame contrast
   ///
   /// [value] contrast value
   void setContrast(int value) {
+    final processor =
+        _processors[VidContrastProcessor] as VidContrastProcessor?;
+    final originVal = processor?.contrast;
+    if (originVal == value) return;
+
     if (value == 0) {
       _processors.remove(VidContrastProcessor);
     } else {
       _processors[VidContrastProcessor] = VidContrastProcessor(value);
     }
     eventHandler.emit(this, VidPlayerContrastChangeEvent(value));
+    _updateFrame();
+  }
+
+  /// 重置图像增益
+  void resetTone() {
+    setBrightness(0);
+    setContrast(0);
+  }
+
+  T? getProcessor<T extends VidFrameProcessor>() {
+    return _processors[T] as T?;
   }
 
-  Future<void> _updateFrame() async {
-    _frame = await _dataHost.getFrame(currentFrameIndex);
+  void _updateFrame() {
+    _handleUpdateFrame
+        .throttle(timeout: _playAssistant?._playIntervalMillSeconds ?? 50)
+        .call();
+  }
+
+  Future<void> _handleUpdateFrame() async {
+    _frame = await _dataHost.getFrame(
+      currentFrameIndex,
+      processors: _processors.values.toList(),
+    );
     eventHandler.emit(
       this,
       VidPlayerFrameIndexChangeEvent(currentFrameIndex, _frame!.imageData),
@@ -174,19 +206,18 @@ class VidPlayerController extends ChangeNotifier {
     eventHandler.emit(this, VidPlayerStatusChangeEvent(status));
   }
 
-  void _stop() {
+  void _stop({bool needNotify = true}) {
     _playAssistant?.pause();
-    _setStatus(VidPlayStatus.stop);
+    if (needNotify) {
+      _setStatus(VidPlayStatus.stop);
+    }
   }
 
   @override
   void dispose() {
-    _stop();
-    _setStatus(VidPlayStatus.dispose);
-    Future.delayed(const Duration(milliseconds: 100), () {
-      eventHandler.dispose();
-      super.dispose();
-    });
+    _stop(needNotify: false);
+    eventHandler.dispose();
+    super.dispose();
   }
 
   @override
@@ -213,6 +244,8 @@ class _PlayAssistant {
 
   int get _playInterval => 1000 * 1000 ~/ _frameRate;
 
+  int get _playIntervalMillSeconds => _playInterval ~/ 1000.0;
+
   Timer? _timer;
 
   void play() {

+ 54 - 1
lib/processors/base.dart

@@ -1 +1,54 @@
-abstract class VidFrameProcessor {}
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:flutter/painting.dart';
+import 'package:image/image.dart' as img;
+import 'package:vid/us/vid_us_image.dart';
+
+abstract class VidFrameProcessor {
+  Uint8List process(Uint8List bytes, int position);
+
+  static Future<VidUsImage> processFrame(
+    VidUsImage frame,
+    List<VidFrameProcessor>? processors,
+  ) async {
+    if (processors == null || processors.isEmpty) return frame;
+
+    try {
+      final start = DateTime.now();
+      final bitmap = await decodeImageFromList(frame.imageData);
+      final buffer = (await bitmap.toByteData())!.buffer.asUint8List();
+      final width = bitmap.width, height = bitmap.height;
+      if (processors.length == 1) {
+        final processor = processors.first;
+        for (int x = 0; x < width; x++) {
+          for (int y = 0; y < height; y++) {
+            int position = ((y * width) + x) * 4;
+            processor.process(buffer, position);
+          }
+        }
+      } else {
+        for (final processor in processors) {
+          for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+              int position = ((y * width) + x) * 4;
+              processor.process(buffer, position);
+            }
+          }
+        }
+      }
+      final dstImg = img.Image.fromBytes(width, height, buffer);
+      final jpgData = img.encodeJpg(dstImg);
+      final dstBuffer = Uint8List.fromList(jpgData);
+
+      final end = DateTime.now();
+      final snap = end.difference(start).inMilliseconds;
+      // print(snap);
+
+      return VidUsImage(frame.index, width, height, dstBuffer);
+    } catch (e) {
+      // ignore
+    }
+    return frame;
+  }
+}

+ 47 - 1
lib/processors/brightness.dart

@@ -1,9 +1,55 @@
+import 'dart:typed_data';
+
 import 'base.dart';
 
 /// Vid亮度处理器
 class VidBrightnessProcessor extends VidFrameProcessor {
-  VidBrightnessProcessor(this.brightness);
+  VidBrightnessProcessor(this.brightness) {
+    _setActualBrightness();
+  }
 
   /// 亮度 [-255~255]
   final int brightness;
+
+  late final int _actualBrightness;
+
+  void _setActualBrightness() {
+    if (brightness < -255) {
+      _actualBrightness = -255;
+    } else if (brightness > 255) {
+      _actualBrightness = 255;
+    } else {
+      _actualBrightness = brightness;
+    }
+  }
+
+  @override
+  Uint8List process(Uint8List bytes, int position) {
+    final i = position;
+    final _brightness = _actualBrightness;
+
+    int b = bytes[i];
+    int g = bytes[i + 1];
+    int r = bytes[i + 2];
+
+    b += _brightness;
+    g += _brightness;
+    r += _brightness;
+
+    if (b < 0) b = 0;
+    if (b > 255) b = 255;
+    if (g < 0) g = 0;
+    if (g > 255) g = 255;
+    if (r < 0) r = 0;
+    if (r > 255) r = 255;
+
+    bytes[i] = b;
+    bytes[i + 1] = g;
+    bytes[i + 2] = r;
+
+    return bytes;
+  }
+
+  @override
+  String toString() => 'VidBrightnessProcessor: $brightness';
 }

+ 63 - 1
lib/processors/contrast.dart

@@ -1,9 +1,71 @@
+import 'dart:typed_data';
+
 import 'base.dart';
 
 /// Vid对比度处理器
 class VidContrastProcessor extends VidFrameProcessor {
-  VidContrastProcessor(this.contrast);
+  VidContrastProcessor(this.contrast) {
+    _setActualContrast();
+  }
 
   /// 对比度 [-99~99]
   final int contrast;
+
+  late final double _actualContrast;
+
+  void _setActualContrast() {
+    double value = contrast.toDouble();
+    if (value < -100) value = -100;
+    if (value > 100) value = 100;
+    value = (100.0 + value) / 100.0;
+    value *= value;
+
+    _actualContrast = value;
+  }
+
+  @override
+  Uint8List process(Uint8List bytes, int position) {
+    final i = position;
+    final _contrast = _actualContrast;
+
+    double b = bytes[i].toDouble();
+    double g = bytes[i + 1].toDouble();
+    double r = bytes[i + 2].toDouble();
+
+    //process b
+    b = b / 255.0;
+    b -= 0.5;
+    b *= _contrast;
+    b += 0.5;
+    b *= 255;
+    if (b < 0) b = 0;
+    if (b > 255) b = 255;
+
+    //process g
+    g = g / 255.0;
+    g -= 0.5;
+    g *= _contrast;
+    g += 0.5;
+    g *= 255;
+    if (g < 0) g = 0;
+    if (g > 255) g = 255;
+
+    //process r
+    r = r / 255.0;
+    r -= 0.5;
+    r *= _contrast;
+    r += 0.5;
+    r *= 255;
+    if (r < 0) r = 0;
+    if (r > 255) r = 255;
+
+    bytes[0] = b.toInt();
+    bytes[1] = g.toInt();
+    bytes[2] = r.toInt();
+
+    return bytes;
+  }
+
+  @override
+  String toString() => 'VidContrastProcessor: $contrast';
 }

+ 78 - 0
lib/vid_player_mtest.dart

@@ -0,0 +1,78 @@
+import 'package:fis_lib_measure/player/control_board/control_board.dart';
+import 'package:fis_lib_measure/player/controller.dart';
+import 'package:fis_lib_measure/player/player.dart';
+import 'package:flutter/material.dart';
+
+import 'data_host/data_host.dart';
+
+class VidPlayerMobilePage extends StatefulWidget {
+  VidPlayerMobilePage(this.url, {Key? key}) : super(key: key);
+
+  final String url;
+
+  @override
+  State<StatefulWidget> createState() => _VidPlayerMobilePageState();
+}
+
+class _VidPlayerMobilePageState extends State<VidPlayerMobilePage> {
+  late final _dataHost = VidDataHost(widget.url);
+  late final _playerController = VidPlayerController(dataHost: _dataHost);
+
+  @override
+  Widget build(BuildContext context) {
+    // _playerController.load().then((value) {
+    //   _playerController.play();
+    // });
+    return FutureBuilder(
+      future: _playerController.load(),
+      builder: (context, snapshot) {
+        if (snapshot.connectionState == ConnectionState.done) {
+          if (_playerController.canPlay) {
+            _playerController.play();
+            return buildPage(context);
+          } else {
+            return const Material(child: Center(child: Text("Load fila")));
+          }
+        } else {
+          return const Material(
+              child: Center(child: CircularProgressIndicator()));
+        }
+      },
+    );
+  }
+
+  Widget buildPage(BuildContext context) {
+    // const borderSide = BorderSide(color: Colors.grey);
+    final size = MediaQuery.of(context).size;
+    final w = size.width;
+    final h = w / 16 * 9;
+    return Scaffold(
+      appBar: AppBar(),
+      body: Center(
+        child: Column(
+          mainAxisSize: MainAxisSize.max,
+          children: [
+            VidPlayer(
+              _playerController,
+              width: w,
+              height: h,
+            ),
+            SizedBox(
+              height: 160,
+              child: _playerController.totalFramesCount > 1
+                  ? VidPlayerControlBoard(_playerController)
+                  : Container(),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _playerController.dispose();
+    _dataHost.release();
+    super.dispose();
+  }
+}

+ 4 - 2
pubspec.yaml

@@ -35,7 +35,7 @@ dependencies:
   fis_common:
     git:
       url: http://git.ius.plus/Project-Wing/fis_lib_common.git
-      ref: ^1.0.5
+      ref: ^1.0.7
   fis_jsonrpc:
     git:
       url: http://git.ius.plus:88/Project-Wing/fis_lib_jsonrpc.git
@@ -46,12 +46,14 @@ dependencies:
   get: ^4.6.1
   quiver: ^3.1.0
   dio: ^4.0.6
+  path_provider: ^2.0.2
+  image: 3.1.3
 
 dependency_overrides:
   fis_common:
     git:
       url: http://git.ius.plus:88/Project-Wing/fis_lib_common.git
-      ref: 1.0.5
+      ref: 1.0.7
 
 dev_dependencies:
   flutter_test: