diff --git a/README.md b/README.md index 157dfd57..a1803691 100755 --- a/README.md +++ b/README.md @@ -112,6 +112,12 @@ const VlcPlayer({ /// This is the initial URL for the content. This also must be provided but [VlcPlayerController] implements /// [VlcPlayerController.setStreamUrl] method so this can be changed at any time. @required this.url, + /// whether the url is a local file instead of network stream url + this.isLocal=false, + /// external subtitle file + this.subtitle="", + /// True: loop the playback forever + this.loop=true, /// Before the platform view has initialized, this placeholder will be rendered instead of the video player. /// This can simply be a [CircularProgressIndicator] (see the example.) this.placeholder, @@ -140,7 +146,7 @@ VlcPlayerController({ /// Returns the current state of the player. /// Valid states: /// - PlayingState.PLAYING - /// - PlayingState.BUFFERING + /// - PlayingState.PAUSED /// - PlayingState.STOPPED /// - null (When the player is uninitialized) PlayingState playingState; @@ -190,6 +196,21 @@ VlcPlayerController({ /// Returns binary data for a snapshot of the media at the current frame. Future takeSnapshot(); + + /// Return subtitle track index list + Future> getSubtitleTracks(); + + /// [track] switch subtitle track by track index + Future setSubtitleTrack(int track); + + /// Return player speed + Future getPlaybackSpeed(); + + /// Return player current postion in milliseconds + Future getTime(); + + /// Return true if player is playing + Future isPlaying(); /// Disposes the platform view and unloads the VLC player. void dispose(); diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3dfedec1..f4039202 100755 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java index 77918b92..cd347f7e 100755 --- a/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java +++ b/android/src/main/java/software/solid/fluttervlcplayer/FlutterVideoView.java @@ -3,25 +3,33 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.PixelFormat; import android.graphics.SurfaceTexture; import android.net.Uri; import android.util.Base64; import android.view.Surface; +import android.view.SurfaceView; import android.view.TextureView; import android.view.View; + import androidx.annotation.NonNull; + import io.flutter.plugin.common.*; import io.flutter.plugin.platform.PlatformView; import io.flutter.view.TextureRegistry; + import org.videolan.libvlc.IVLCVout; import org.videolan.libvlc.LibVLC; import org.videolan.libvlc.Media; import org.videolan.libvlc.MediaPlayer; +import org.videolan.libvlc.util.VLCVideoLayout; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.io.File; class FlutterVideoView implements PlatformView, MethodChannel.MethodCallHandler, MediaPlayer.EventListener { @@ -38,6 +46,7 @@ class FlutterVideoView implements PlatformView, MethodChannel.MethodCallHandler, private LibVLC libVLC; private MediaPlayer mediaPlayer; + private VLCVideoLayout frameLayout; private TextureView textureView; private IVLCVout vout; private boolean playerDisposed; @@ -52,83 +61,90 @@ public FlutterVideoView(Context context, PluginRegistry.Registrar _registrar, Bi eventChannel = new EventChannel(messenger, "flutter_video_plugin/getVideoEvents_" + id); eventChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink sink) { - eventSink.setDelegate(sink); - } + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink sink) { + eventSink.setDelegate(sink); + } - @Override - public void onCancel(Object o) { - eventSink.setDelegate(null); + @Override + public void onCancel(Object o) { + eventSink.setDelegate(null); + } } - } ); TextureRegistry.SurfaceTextureEntry textureEntry = registrar.textures().createSurfaceTexture(); - textureView = new TextureView(context); + createLayout(context); textureView.setSurfaceTexture(textureEntry.surfaceTexture()); - textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener(){ - - boolean wasPaused = false; - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - if(vout == null) return; - - vout.setVideoSurface(new Surface(textureView.getSurfaceTexture()), null); - vout.attachViews(); - textureView.forceLayout(); - if(wasPaused){ - mediaPlayer.play(); - wasPaused = false; - } - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - if(playerDisposed){ - if(mediaPlayer != null) { - mediaPlayer.stop(); - mediaPlayer.release(); - mediaPlayer = null; - } - return true; - }else{ - if(mediaPlayer != null && vout != null) { - mediaPlayer.pause(); - wasPaused = true; - vout.detachViews(); - } - return true; - } - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - - } - - }); +// +// textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { +// +// boolean wasPaused = false; +// +// @Override +// public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { +// if (vout == null) return; +// +// vout.setVideoSurface(new Surface(textureView.getSurfaceTexture()), null); +// vout.attachViews(); +// textureView.forceLayout(); +// if (wasPaused) { +// mediaPlayer.play(); +// wasPaused = false; +// } +// } +// +// @Override +// public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { +// +// } +// +// @Override +// public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { +// if (playerDisposed) { +// if (mediaPlayer != null) { +// mediaPlayer.stop(); +// mediaPlayer.release(); +// mediaPlayer = null; +// } +// return true; +// } else { +// if (mediaPlayer != null && vout != null) { +// mediaPlayer.pause(); +// wasPaused = true; +// vout.detachViews(); +// } +// return true; +// } +// } +// +// @Override +// public void onSurfaceTextureUpdated(SurfaceTexture surface) { +// +// } +// +// }); methodChannel = new MethodChannel(messenger, "flutter_video_plugin/getVideoView_" + id); methodChannel.setMethodCallHandler(this); } + private void createLayout(Context context) { + frameLayout = new VLCVideoLayout(context); + textureView = new TextureView(context); + frameLayout.addView(textureView); + } + @Override public View getView() { - return textureView; + return frameLayout; } @Override public void dispose() { - if(mediaPlayer != null) mediaPlayer.stop(); - if(vout != null) vout.detachViews(); + if (mediaPlayer != null) mediaPlayer.stop(); + if (vout != null) vout.detachViews(); playerDisposed = true; } @@ -138,21 +154,38 @@ public void dispose() { @SuppressLint("WrongThread") @Override public void onMethodCall(MethodCall methodCall, @NonNull MethodChannel.Result result) { + Boolean isLocal = false; + long time = 0; + float rate = (float) 1.0; + int track = -1; + String subtitle = ""; + Boolean loop = false; + Boolean playing = false; switch (methodCall.method) { case "initialize": - if (textureView == null) { - textureView = new TextureView(context); + if (frameLayout == null) { + createLayout(context); } ArrayList options = new ArrayList<>(); options.add("--no-drop-late-frames"); options.add("--no-skip-frames"); - options.add("--rtsp-tcp"); +// options.add("--android-display-chroma"); +// options.add("RV16"); +// options.add("--vout=android_display,none"); - if(DISABLE_LOG_OUTPUT) { + if (DISABLE_LOG_OUTPUT) { // Silence player log output. options.add("--quiet"); } + String initStreamURL = methodCall.argument("url"); + isLocal = methodCall.argument("isLocal"); + subtitle = methodCall.argument("subtitle"); + loop = methodCall.argument("loop"); + if (loop) + options.add("--input-repeat=65535"); + if (!isLocal) + options.add("--rtsp-tcp"); libVLC = new LibVLC(context, options); mediaPlayer = new MediaPlayer(libVLC); @@ -161,12 +194,20 @@ public void onMethodCall(MethodCall methodCall, @NonNull MethodChannel.Result re vout = mediaPlayer.getVLCVout(); textureView.forceLayout(); textureView.setFitsSystemWindows(true); - vout.setVideoSurface(new Surface(textureView.getSurfaceTexture()), null); + //vout.setVideoSurface(new Surface(textureView.getSurfaceTexture()), null); + vout.setVideoView(textureView); vout.attachViews(); - String initStreamURL = methodCall.argument("url"); - Media media = new Media(libVLC, Uri.parse(Uri.decode(initStreamURL))); + Media media = null; + if (isLocal) + media = new Media(libVLC, Uri.fromFile(new File(initStreamURL))); + else { + media = new Media(libVLC, Uri.parse(Uri.decode(initStreamURL))); + } + mediaPlayer.setMedia(media); + if (!subtitle.isEmpty()) + mediaPlayer.addSlave(Media.Slave.Type.Subtitle, subtitle, true); result.success(null); break; @@ -174,12 +215,23 @@ public void onMethodCall(MethodCall methodCall, @NonNull MethodChannel.Result re this.dispose(); break; case "changeURL": - if(libVLC == null) result.error("VLC_NOT_INITIALIZED", "The player has not yet been initialized.", false); + if (libVLC == null) + result.error("VLC_NOT_INITIALIZED", "The player has not yet been initialized.", false); - mediaPlayer.stop(); + playing=mediaPlayer.isPlaying(); + if (playing) + mediaPlayer.stop(); String newURL = methodCall.argument("url"); - Media newMedia = new Media(libVLC, Uri.parse(Uri.decode(newURL))); + isLocal = methodCall.argument("isLocal"); + Media newMedia = null; + if (isLocal) + newMedia = new Media(libVLC, Uri.fromFile(new File(newURL))); + else + newMedia = new Media(libVLC, Uri.parse(Uri.decode(newURL))); + newMedia.setHWDecoderEnabled(true, true); mediaPlayer.setMedia(newMedia); + //if (playing) + mediaPlayer.play(); result.success(null); break; @@ -198,15 +250,17 @@ public void onMethodCall(MethodCall methodCall, @NonNull MethodChannel.Result re case "setPlaybackState": String playbackState = methodCall.argument("playbackState"); - if(playbackState == null) result.success(null); + if (playbackState == null) result.success(null); - switch(playbackState){ + switch (playbackState) { case "play": textureView.forceLayout(); - mediaPlayer.play(); + if (!mediaPlayer.isPlaying()) + mediaPlayer.play(); break; case "pause": - mediaPlayer.pause(); + if (mediaPlayer.isPlaying()) + mediaPlayer.pause(); break; case "stop": mediaPlayer.stop(); @@ -223,14 +277,45 @@ public void onMethodCall(MethodCall methodCall, @NonNull MethodChannel.Result re result.success(null); break; + case "getPlaybackSpeed": + rate = mediaPlayer.getRate(); + result.success(rate); + break; case "setTime": - - long time = Long.parseLong((String) methodCall.argument("time")); + time = Long.parseLong((String) methodCall.argument("time")); mediaPlayer.setTime(time); result.success(null); break; + case "getTime": + time = mediaPlayer.getTime(); + result.success(time); + break; + case "getDuration": + time = mediaPlayer.getLength(); + result.success(time); + break; + case "isPlaying": + result.success(mediaPlayer.isPlaying()); + break; + case "setSubtitleTrack": + track = methodCall.argument("track"); + mediaPlayer.setSpuTrack(track); + break; + case "getSubtitleTracks": + MediaPlayer.TrackDescription[] tracks = mediaPlayer.getSpuTracks(); + List list = new ArrayList(); + for (MediaPlayer.TrackDescription t : tracks) { + if (t.id >= 0) + list.add(t.id); + } + result.success(list); + break; + case "addSubtitle": + subtitle = methodCall.argument("subtitle"); + mediaPlayer.addSlave(Media.Slave.Type.Subtitle, subtitle, true); + break; } } @@ -251,7 +336,7 @@ public void onEvent(MediaPlayer.Event event) { int width = 0; Media.VideoTrack currentVideoTrack = (Media.VideoTrack) mediaPlayer.getMedia().getTrack( - mediaPlayer.getVideoTrack() + mediaPlayer.getVideoTrack() ); if (currentVideoTrack != null) { height = currentVideoTrack.height; @@ -266,7 +351,13 @@ public void onEvent(MediaPlayer.Event event) { eventObject.put("length", mediaPlayer.getLength()); eventSink.success(eventObject.clone()); break; - + case MediaPlayer.Event.PositionChanged: + float pos = event.getPositionChanged(); + eventObject.put("name", "position"); + eventObject.put("value", pos); + eventSink.success(eventObject.clone()); + eventObject.clear(); + break; case MediaPlayer.Event.EndReached: mediaPlayer.stop(); eventObject.put("name", "ended"); @@ -277,7 +368,7 @@ public void onEvent(MediaPlayer.Event event) { eventObject.put("value", false); eventObject.put("reason", "EndReached"); eventSink.success(eventObject); - + case MediaPlayer.Event.Vout: vout.setWindowSize(textureView.getWidth(), textureView.getHeight()); break; @@ -292,14 +383,14 @@ public void onEvent(MediaPlayer.Event event) { case MediaPlayer.Event.EncounteredError: System.err.println("(flutter_vlc_plugin) A VLC error occurred."); case MediaPlayer.Event.Paused: - case MediaPlayer.Event.Stopped: - eventObject.put("name", "buffering"); - eventObject.put("value", false); + eventObject.clear(); + eventObject.put("name", "paused"); + eventObject.put("value", true); eventSink.success(eventObject); - + case MediaPlayer.Event.Stopped: eventObject.clear(); - eventObject.put("name", "playing"); - eventObject.put("value", false); + eventObject.put("name", "stopped"); + eventObject.put("value", true); eventSink.success(eventObject); break; } diff --git a/ios/Classes/FlutterVlcPlayerPlugin.m b/ios/Classes/FlutterVlcPlayerPlugin.m index 7d120c32..117e81d6 100755 --- a/ios/Classes/FlutterVlcPlayerPlugin.m +++ b/ios/Classes/FlutterVlcPlayerPlugin.m @@ -33,13 +33,28 @@ + (instancetype)initWithChannels: (FlutterMethodChannel*) methodChannel andEvent if([call.method isEqualToString:@"initialize"]) { NSString *url = call.arguments[@"url"]; - - VLCMediaPlayer *player = [[VLCMediaPlayer alloc] init]; + bool isLocal = [call.arguments[@"isLocal"] boolValue]; + NSString *subtitle = call.arguments[@"subtitle"]; + bool loop = [call.arguments[@"loop"] boolValue]; + NSMutableArray *options= [[NSMutableArray alloc] init]; + if (!isLocal) + [options addObject:@"--rtsp-tcp"]; + if (loop) + [options addObject:@"--input-repeat=65535"]; + VLCMediaPlayer *player = [[VLCMediaPlayer alloc] initWithOptions:options]; player.delegate = eventChannelHandler; instance.player = player; - VLCMedia *media = [VLCMedia mediaWithURL:[NSURL URLWithString:url]]; + VLCMedia *media = nil; + if (isLocal) + media = [VLCMedia mediaWithPath:url]; + else + media = [VLCMedia mediaWithURL:[NSURL URLWithString:url]]; + + //add subtitle + if ([subtitle length] > 0) + [player addPlaybackSlave:[NSURL URLWithString:subtitle] type:VLCMediaPlaybackSlaveTypeSubtitle enforce:true]; player.media = media; player.position = 0.5; player.drawable = instance.hostedView; @@ -63,11 +78,25 @@ + (instancetype)initWithChannels: (FlutterMethodChannel*) methodChannel andEvent return; } - [instance.player stop]; + bool isplaying=[instance.player isPlaying]; + if (isplaying) + [instance.player stop]; NSString *url = call.arguments[@"url"]; - VLCMedia *media = [VLCMedia mediaWithURL:[NSURL URLWithString:url]]; + bool isLocal = [call.arguments[@"isLocal"] boolValue]; + NSString *subtitle = call.arguments[@"subtitle"]; + VLCMedia *media = nil; + if (isLocal) + media = [VLCMedia mediaWithPath:url]; + else + media = [VLCMedia mediaWithURL:[NSURL URLWithString:url]]; + + //add subtitle + if ([subtitle length] > 0) + [instance.player addPlaybackSlave:[NSURL URLWithString:subtitle] type:VLCMediaPlaybackSlaveTypeSubtitle enforce:true]; instance.player.media = media; + //if (isplaying) + [instance.player play]; result(nil); return; @@ -93,11 +122,14 @@ + (instancetype)initWithChannels: (FlutterMethodChannel*) methodChannel andEvent NSString *playbackState = call.arguments[@"playbackState"]; if([playbackState isEqualToString:@"play"]) { - [instance.player play]; + if (![instance.player isPlaying]) + [instance.player play]; } else if ([playbackState isEqualToString:@"pause"]) { - [instance.player pause]; + if ([instance.player isPlaying]) + [instance.player pause]; } else if ([playbackState isEqualToString:@"stop"]) { - [instance.player stop]; + if ([instance.player isPlaying]) + [instance.player stop]; } result(nil); @@ -112,7 +144,14 @@ + (instancetype)initWithChannels: (FlutterMethodChannel*) methodChannel andEvent result(nil); return; - } else if ([call.method isEqualToString:@"setTime"]) { + } else if ([call.method isEqualToString:@"getPlaybackSpeed"]) { + + float rate= instance.player.rate; + + result([NSNumber numberWithDouble:rate]); + return; + + }else if ([call.method isEqualToString:@"setTime"]) { VLCTime *time = [VLCTime timeWithNumber:call.arguments[@"time"]]; instance.player.time = time; @@ -120,7 +159,52 @@ + (instancetype)initWithChannels: (FlutterMethodChannel*) methodChannel andEvent result(nil); return; - } + } else if ([call.method isEqualToString:@"getTime"]) { + + int value= instance.player.time.intValue; + + result([NSNumber numberWithInt:value]); + return; + + } else if ([call.method isEqualToString:@"getDuration"]) { + + int value= instance.player.media.length.intValue; + + result([NSNumber numberWithInt:value]); + return; + + }else if ([call.method isEqualToString:@"isPlaying"]) { + + bool value= [instance.player isPlaying]; + + result([NSNumber numberWithBool:value]); + return; + + }else if ([call.method isEqualToString:@"setSubtitleTrack"]) { + + NSNumber* value=call.arguments[@"track"]; + int track = value.intValue; + instance.player.currentVideoSubTitleIndex = track; + + return; + }else if ([call.method isEqualToString:@"getSubtitleTracks"]) { + + NSArray *videoSubTitlesIndexes = instance.player.videoSubTitlesIndexes; + NSMutableArray *subtitles=[NSMutableArray array]; + for (NSNumber* n in videoSubTitlesIndexes){ + if (n.intValue>=0) + [subtitles addObject:n]; + } + + result(subtitles); + return; + }else if ([call.method isEqualToString:@"addSubtitle"]) { + + NSString* subtitle = call.arguments[@"subtitle"]; + [instance.player addPlaybackSlave:[NSURL URLWithString:subtitle] type:VLCMediaPlaybackSlaveTypeSubtitle enforce:true]; + + return; + } }]; @@ -138,7 +222,7 @@ -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS if (self.aspectSet) return; if (!self.player.isPlaying) return; - + [_player setDrawable:_hostedView]; [_player setVideoAspectRatio:"0.7"]; [_player setCurrentVideoTrackIndex:0]; @@ -222,7 +306,8 @@ - (void)mediaPlayerStateChanged:(NSNotification *)aNotification { ratio = width.floatValue / height.floatValue; } } - + + switch(player.state){ case VLCMediaPlayerStateESAdded: case VLCMediaPlayerStateBuffering: @@ -230,11 +315,6 @@ - (void)mediaPlayerStateChanged:(NSNotification *)aNotification { return; case VLCMediaPlayerStatePlaying: - _eventSink(@{ - @"name": @"buffering", - @"value": @(NO) - }); - _eventSink(@{ @"name": @"playing", @"value": @(YES), @@ -246,8 +326,9 @@ - (void)mediaPlayerStateChanged:(NSNotification *)aNotification { return; case VLCMediaPlayerStateEnded: + [player stop]; _eventSink(@{ - @"name": @"ended" + @"name": @"ended", }); _eventSink(@{ @@ -262,16 +343,17 @@ - (void)mediaPlayerStateChanged:(NSNotification *)aNotification { return; case VLCMediaPlayerStatePaused: - case VLCMediaPlayerStateStopped: _eventSink(@{ - @"name": @"buffering", - @"value": @(NO) + @"name": @"paused", + @"value": @(YES) }); - + return; + case VLCMediaPlayerStateStopped: _eventSink(@{ - @"name": @"playing", - @"value": @(NO) + @"name": @"stopped", + @"value": @(YES) }); + return; } } @@ -284,6 +366,14 @@ - (void)mediaPlayerTimeChanged:(NSNotification *)aNotification { @"name": @"timeChanged", @"value": player.time.value, @"speed": @(player.rate), + @"length": player.media.length.value ?: @0 + }); + + _eventSink(@{ + @"name": @"position", + @"value": player.time.value, + @"speed": @(player.rate), + @"length": player.media.length.value ?: @0 }); return; diff --git a/lib/flutter_vlc_player.dart b/lib/flutter_vlc_player.dart index 033efb3a..194a6191 100755 --- a/lib/flutter_vlc_player.dart +++ b/lib/flutter_vlc_player.dart @@ -8,7 +8,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -enum PlayingState { STOPPED, BUFFERING, PLAYING } +typedef StatusChanged = void Function(String status, T value); + +enum PlayingState { STOPPED, PAUSED, PLAYING } class Size { final int width; @@ -24,6 +26,9 @@ class Size { class VlcPlayer extends StatefulWidget { final double aspectRatio; final String url; + final bool isLocal; + final String subtitle; + final bool loop; final Widget placeholder; final VlcPlayerController controller; @@ -41,6 +46,9 @@ class VlcPlayer extends StatefulWidget { /// This is the initial URL for the content. This also must be provided but [VlcPlayerController] implements /// [VlcPlayerController.setStreamUrl] method so this can be changed at any time. @required this.url, + this.isLocal=false, + this.subtitle="", + this.loop=true, /// Before the platform view has initialized, this placeholder will be rendered instead of the video player. /// This can simply be a [CircularProgressIndicator] (see the example.) @@ -120,7 +128,7 @@ class _VlcPlayerState extends State // Once the controller has clients registered, we're good to register // with LibVLC on the platform side. if (_controller.hasClients) { - await _controller._initialize(widget.url); + await _controller._initialize(widget.url, widget.isLocal, widget.subtitle, widget.loop); } } @@ -145,6 +153,8 @@ class VlcPlayerController { VoidCallback _onInit; List _eventHandlers; + StatusChanged onStatusChanged=null; + /// Once the [_methodChannel] and [_eventChannel] have been registered with /// the Flutter platform SDK counterparts, [hasClients] is set to true. /// At this point, the player is ready to begin playing content. @@ -217,7 +227,9 @@ class VlcPlayerController { /// This is a callback that will be executed once the platform view has been initialized. /// If you want the media to play as soon as the platform view has initialized, you could just call /// [VlcPlayerController.play] in this callback. (see the example) - VoidCallback onInit}) { + VoidCallback onInit, + this.onStatusChanged + }) { _onInit = onInit; _eventHandlers = new List(); } @@ -244,10 +256,10 @@ class VlcPlayerController { _eventHandlers.forEach((handler) => handler()); } - Future _initialize(String url) async { + Future _initialize(String url, bool isLocal, String subtitle, bool loop) async { //if(initialized) throw new Exception("Player already initialized!"); - await _methodChannel.invokeMethod("initialize", {'url': url}); + await _methodChannel.invokeMethod("initialize", {'url': url, 'isLocal':isLocal, 'subtitle':subtitle, 'loop':loop}); _position = 0; _eventChannel.receiveBroadcastStream().listen((event) { @@ -258,23 +270,34 @@ class VlcPlayerController { if (event['length'] != null) _duration = event['length']; if (event['ratio'] != null) _aspectRatio = event['ratio']; - _playingState = - event['value'] ? PlayingState.PLAYING : PlayingState.STOPPED; - + _playingState = PlayingState.PLAYING; _fireEventHandlers(); break; - case 'buffering': - if (event['value']) _playingState = PlayingState.BUFFERING; + case 'paused': + _playingState = PlayingState.PAUSED; + _fireEventHandlers(); + break; + case 'stopped': + _playingState = PlayingState.STOPPED; _fireEventHandlers(); break; case 'timeChanged': _position = event['value']; + if (event['length']!=null) + _duration = event['length']; _playbackSpeed = event['speed']; _fireEventHandlers(); break; + case 'position': + //_position = event['value']; + _fireEventHandlers(); + break; } + + if (this.onStatusChanged!=null) + this.onStatusChanged(event['name'], event['value']); }); _initialized = true; @@ -282,13 +305,13 @@ class VlcPlayerController { _onInit(); } - Future setStreamUrl(String url) async { + Future setStreamUrl(String url, bool isLocal, String subtitle) async { _initialized = false; _fireEventHandlers(); - bool wasPlaying = _playingState != PlayingState.STOPPED; - await _methodChannel.invokeMethod("changeURL", {'url': url}); - if (wasPlaying) play(); + //bool wasPlaying = _playingState != PlayingState.STOPPED; + await _methodChannel.invokeMethod("changeURL", {'url': url, 'isLocal':isLocal, 'subtitle':subtitle}); + //if (wasPlaying) play(); _initialized = true; _fireEventHandlers(); @@ -309,15 +332,31 @@ class VlcPlayerController { .invokeMethod("setPlaybackState", {'playbackState': 'stop'}); } + Future isPlaying() async { + var result = await _methodChannel.invokeMethod("isPlaying"); + return result; + } + Future setTime(int time) async { await _methodChannel.invokeMethod("setTime", {'time': time.toString()}); } + //return ms position + Future getTime() async { + var result = await _methodChannel.invokeMethod("getTime"); + return result; + } + Future setPlaybackSpeed(double speed) async { await _methodChannel .invokeMethod("setPlaybackSpeed", {'speed': speed.toString()}); } + Future getPlaybackSpeed() async { + var result = await _methodChannel.invokeMethod("getPlaybackSpeed"); + return result; + } + Future takeSnapshot() async { var result = await _methodChannel.invokeMethod("getSnapshot"); var base64String = result['snapshot']; @@ -325,6 +364,18 @@ class VlcPlayerController { return imageBytes; } + Future setSubtitleTrack(int track) async { + await _methodChannel.invokeMethod("setSubtitleTrack", { + 'track': track, + }); + } + + Future> getSubtitleTracks() async { + List list=await _methodChannel.invokeMethod("getSubtitleTracks"); + return list; + } + + void dispose() { _methodChannel.invokeMethod("dispose"); }