From 9b724961ca18332553b14cd73474f26a66e3fa24 Mon Sep 17 00:00:00 2001 From: SUNNET-chin-shou Date: Tue, 28 Apr 2020 14:37:26 +0900 Subject: [PATCH 1/2] support playback local file on android --- .../solid/fluttervlcplayer/FlutterVideoView.java | 14 ++++++++++++-- lib/vlc_player.dart | 4 +++- lib/vlc_player_controller.dart | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java index 5463bb4c..0bbe719a 100644 --- a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java +++ b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.io.File; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; @@ -32,6 +33,7 @@ class FlutterVideoView implements PlatformView, MethodChannel.MethodCallHandler, private MediaPlayer mediaPlayer; private TextureView textureView; private String url; + private Boolean isLocal; private IVLCVout vout; private MethodChannel.Result result; private boolean replyAlreadySubmitted = false; @@ -67,14 +69,22 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { textureView = new TextureView(context); } url = methodCall.argument("url"); + isLocal = methodCall.argument("isLocal"); ArrayList options = new ArrayList<>(); options.add("--no-drop-late-frames"); options.add("--no-skip-frames"); - options.add("--rtsp-tcp"); LibVLC libVLC = new LibVLC(context, options); - Media media = new Media(libVLC, Uri.parse(Uri.decode(url))); + Media media = null; + if (isLocal) + media = new Media(libVLC, Uri.fromFile(new File(url))); + else { + options.add("--rtsp-tcp"); + media = new Media(libVLC, Uri.parse(Uri.decode(url))); + } + media.setHWDecoderEnabled(true, true); + mediaPlayer = new MediaPlayer(libVLC); mediaPlayer.setVideoTrackEnabled(true); vout = mediaPlayer.getVLCVout(); diff --git a/lib/vlc_player.dart b/lib/vlc_player.dart index 1f1f0116..1593f4a9 100644 --- a/lib/vlc_player.dart +++ b/lib/vlc_player.dart @@ -9,6 +9,7 @@ class VlcPlayer extends StatefulWidget { final int defaultHeight; final int defaultWidth; final String url; + final bool isLocal; final Widget placeholder; final VlcPlayerController controller; @@ -17,6 +18,7 @@ class VlcPlayer extends StatefulWidget { @required this.defaultHeight, @required this.defaultWidth, @required this.url, + this.isLocal=false, @required this.controller, this.placeholder, }); @@ -71,7 +73,7 @@ class _VlcPlayerState extends State { _controller.initView(id); if (_controller.hasClients) { String aspectRatioString = await _controller.setStreamUrl( - widget.url, widget.defaultHeight, widget.defaultWidth); + widget.url, widget.isLocal, widget.defaultHeight, widget.defaultWidth); setState(() { readyToShow = true; }); diff --git a/lib/vlc_player_controller.dart b/lib/vlc_player_controller.dart index 6c21b826..0450bb55 100644 --- a/lib/vlc_player_controller.dart +++ b/lib/vlc_player_controller.dart @@ -13,9 +13,10 @@ class VlcPlayerController { } Future setStreamUrl( - String url, int defaultHeight, int defaultWidth) async { + String url, bool isLocal, int defaultHeight, int defaultWidth) async { var result = await _channel.invokeMethod("playVideo", { 'url': url, + 'isLocal':isLocal }); return result['aspectRatio']; } From f358999ff3cc8d87d4e04965dc133a6483f0d0da Mon Sep 17 00:00:00 2001 From: SUNNET-chin-shou Date: Fri, 1 May 2020 22:27:10 +0900 Subject: [PATCH 2/2] 1. Add pause , play , etc methods for vlc player controller 2. Add event on position change , end of playback to vlc player controller --- .../fluttervlcplayer/FlutterVideoView.java | 76 ++++++++++++++++++- lib/vlc_player.dart | 4 +- lib/vlc_player_controller.dart | 67 ++++++++++++++-- 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java index 0bbe719a..223cf09b 100644 --- a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java +++ b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java @@ -25,15 +25,25 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.EventChannel.EventSink; +import io.flutter.plugin.common.EventChannel.StreamHandler; -class FlutterVideoView implements PlatformView, MethodChannel.MethodCallHandler, MediaPlayer.EventListener { +class FlutterVideoView implements PlatformView, MethodChannel.MethodCallHandler,StreamHandler, MediaPlayer.EventListener { private final MethodChannel channel; + private final EventChannel eventChannel; private final Context context; private MediaPlayer mediaPlayer; private TextureView textureView; private String url; + private String subtitle; private Boolean isLocal; + private int duration; + private int position; + private EventSink events; + private Boolean isPlaying; + private float rate; private IVLCVout vout; private MethodChannel.Result result; private boolean replyAlreadySubmitted = false; @@ -45,7 +55,9 @@ public FlutterVideoView(Context context, BinaryMessenger messenger, int id) { SurfaceTexture texture = new SurfaceTexture(false); textureView.setSurfaceTexture(texture); channel = new MethodChannel(messenger, "flutter_video_plugin/getVideoView_" + id); + eventChannel = new EventChannel(messenger, "flutter_video_plugin/event_"+id); channel.setMethodCallHandler(this); + eventChannel.setStreamHandler(this); } @Override @@ -59,6 +71,16 @@ public void dispose() { vout.detachViews(); } + @Override + public void onListen(Object arguments, EventSink events) { + this.events = events; + } + + @Override + public void onCancel(Object arguments) { + this.events=null; + } + @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { @@ -70,6 +92,7 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { } url = methodCall.argument("url"); isLocal = methodCall.argument("isLocal"); + subtitle = methodCall.argument("subtitle"); ArrayList options = new ArrayList<>(); options.add("--no-drop-late-frames"); @@ -94,6 +117,9 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { vout.attachViews(); mediaPlayer.setMedia(media); + if (!subtitle.isEmpty()) + mediaPlayer.addSlave(Media.Slave.Type.Subtitle, subtitle, true); + mediaPlayer.setEventListener(this); mediaPlayer.play(); break; @@ -122,6 +148,39 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { mediaPlayer.play(); } break; + case "pause": + if (mediaPlayer.isPlaying()) { + mediaPlayer.pause(); + } + break; + case "play": + if (!mediaPlayer.isPlaying()) { + mediaPlayer.play(); + } + break; + case "isPlaying": + result.success(mediaPlayer.isPlaying()); + break; + case "setRate": + rate = methodCall.argument("rate"); + mediaPlayer.setRate(rate); + break; + case "getRate": + rate = mediaPlayer.getRate(); + result.success(rate); + break; + case "getDuration": + duration = (int)mediaPlayer.getLength(); + result.success(duration); + break; + case "getPosition": + position = (int)mediaPlayer.getTime(); + result.success(position); + break; + case "addSubtitle": + subtitle = methodCall.argument("subtitle"); + mediaPlayer.addSlave(Media.Slave.Type.Subtitle, subtitle, true); + break; } } @@ -130,6 +189,21 @@ public void onEvent(MediaPlayer.Event event) { Map resultMap = new HashMap<>(); switch (event.type) { + case MediaPlayer.Event.EndReached: + if (this.events!=null){ + resultMap.put("status", "end"); + resultMap.put("value", ""); + events.success(resultMap); + } + break; + case MediaPlayer.Event.PositionChanged: + if (this.events!=null){ + float pos = event.getPositionChanged(); + resultMap.put("status", "pos"); + resultMap.put("value", Float.toString(pos)); + events.success(resultMap); + } + break; case MediaPlayer.Event.Vout: String aspectRatio; int height = 0; diff --git a/lib/vlc_player.dart b/lib/vlc_player.dart index 1593f4a9..54a1032c 100644 --- a/lib/vlc_player.dart +++ b/lib/vlc_player.dart @@ -10,6 +10,7 @@ class VlcPlayer extends StatefulWidget { final int defaultWidth; final String url; final bool isLocal; + final String subtitle; final Widget placeholder; final VlcPlayerController controller; @@ -19,6 +20,7 @@ class VlcPlayer extends StatefulWidget { @required this.defaultWidth, @required this.url, this.isLocal=false, + this.subtitle='', @required this.controller, this.placeholder, }); @@ -73,7 +75,7 @@ class _VlcPlayerState extends State { _controller.initView(id); if (_controller.hasClients) { String aspectRatioString = await _controller.setStreamUrl( - widget.url, widget.isLocal, widget.defaultHeight, widget.defaultWidth); + widget.url, widget.isLocal, widget.subtitle, widget.defaultHeight, widget.defaultWidth); setState(() { readyToShow = true; }); diff --git a/lib/vlc_player_controller.dart b/lib/vlc_player_controller.dart index 0450bb55..5326a588 100644 --- a/lib/vlc_player_controller.dart +++ b/lib/vlc_player_controller.dart @@ -2,22 +2,43 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:cryptoutils/cryptoutils.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +typedef StatusChanged = void Function(String status, T value); class VlcPlayerController { MethodChannel _channel; + EventChannel _eventChannel; + + StatusChanged onStatusChanged; + bool hasClients = false; + VlcPlayerController({ + Key key, + this.onStatusChanged=null + }); + initView(int id) { _channel = MethodChannel("flutter_video_plugin/getVideoView_$id"); + _eventChannel = EventChannel("flutter_video_plugin/event_$id"); + _eventChannel.receiveBroadcastStream().listen((dynamic event) + { + if (onStatusChanged!=null){ + + String status=event['status']; + onStatusChanged(status, event['value']); + } + } + ); + hasClients = true; } - Future setStreamUrl( - String url, bool isLocal, int defaultHeight, int defaultWidth) async { - var result = await _channel.invokeMethod("playVideo", { - 'url': url, - 'isLocal':isLocal - }); + Future setStreamUrl(String url, bool isLocal, String subtitle, + int defaultHeight, int defaultWidth) async { + var result = await _channel.invokeMethod( + "playVideo", {'url': url, 'isLocal': isLocal, 'subtitle': subtitle}); return result['aspectRatio']; } @@ -28,6 +49,40 @@ class VlcPlayerController { return imageBytes; } + Future play() async { + var result = await _channel.invokeMethod("play"); + } + + Future pause() async { + var result = await _channel.invokeMethod("pause"); + } + + Future isPlaying() async { + var result = await _channel.invokeMethod("isPlaying"); + return result; + } + + Future getDuration() async { + var result = await _channel.invokeMethod("getDuration"); + return result; + } + + Future getPosition() async { + var result = await _channel.invokeMethod("getPosition"); + return result; + } + + Future setRate(double rate) async { + var result = await _channel.invokeMethod("setRate", { + 'rate': rate, + }); + } + + Future getRate() async { + var result = await _channel.invokeMethod("getRate"); + return result; + } + void dispose() { if (Platform.isIOS) { _channel.invokeMethod("dispose");