diff --git a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java index 5463bb4c..223cf09b 100644 --- a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java +++ b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java @@ -19,19 +19,31 @@ 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; 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; @@ -43,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 @@ -57,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) { @@ -67,14 +91,23 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { textureView = new TextureView(context); } url = methodCall.argument("url"); + isLocal = methodCall.argument("isLocal"); + subtitle = methodCall.argument("subtitle"); 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(); @@ -84,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; @@ -112,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; } } @@ -120,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 1f1f0116..54a1032c 100644 --- a/lib/vlc_player.dart +++ b/lib/vlc_player.dart @@ -9,6 +9,8 @@ class VlcPlayer extends StatefulWidget { final int defaultHeight; final int defaultWidth; final String url; + final bool isLocal; + final String subtitle; final Widget placeholder; final VlcPlayerController controller; @@ -17,6 +19,8 @@ class VlcPlayer extends StatefulWidget { @required this.defaultHeight, @required this.defaultWidth, @required this.url, + this.isLocal=false, + this.subtitle='', @required this.controller, this.placeholder, }); @@ -71,7 +75,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.subtitle, widget.defaultHeight, widget.defaultWidth); setState(() { readyToShow = true; }); diff --git a/lib/vlc_player_controller.dart b/lib/vlc_player_controller.dart index 6c21b826..5326a588 100644 --- a/lib/vlc_player_controller.dart +++ b/lib/vlc_player_controller.dart @@ -2,21 +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, int defaultHeight, int defaultWidth) async { - var result = await _channel.invokeMethod("playVideo", { - 'url': url, - }); + 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']; } @@ -27,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");