diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 240f2f0f..00000000 --- a/.github/funding.yml +++ /dev/null @@ -1,13 +0,0 @@ - -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: solidsoftwarehq -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] \ No newline at end of file diff --git a/.github/workflows/code_check_for_flutter_vlc_player.yaml b/.github/workflows/code_check_for_flutter_vlc_player.yaml new file mode 100644 index 00000000..75b3c46f --- /dev/null +++ b/.github/workflows/code_check_for_flutter_vlc_player.yaml @@ -0,0 +1,58 @@ +name: Library ON Push & PR DO Code check +on: [push, pull_request] + +jobs: + code-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Setup flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - name: Check flutter sdk version + run: flutter --version + + - name: Get dependencies + working-directory: ./flutter_vlc_player + run: flutter pub get + + - name: Setup Dart Code Metrics + working-directory: ./flutter_vlc_player + run: dart pub get dart_code_metrics + + - name: Dart Code Metrics + working-directory: ./flutter_vlc_player + run: | + dirs_to_analyze="" + if [ -d lib ]; then dirs_to_analyze+=" lib"; fi + if [ -d test ]; then dirs_to_analyze+=" test"; fi + if [ -d example ]; then dirs_to_analyze+=" example"; fi + if [ dirs_to_analyze != "" ] + then + dart run dart_code_metrics:metrics \ + analyze \ + $dirs_to_analyze \ + --fatal-warnings \ + --fatal-performance \ + --fatal-style + dart run dart_code_metrics:metrics \ + check-unused-files \ + $dirs_to_analyze \ + --fatal-unused + fi + - name: Check formatting + run: dart format . --set-exit-if-changed + + - name: Run tests + run: | + # run tests if `test` folder exists + if [ -d test ] + then + flutter test -r expanded + else + echo "Tests not found." + fi diff --git a/.github/workflows/code_check_for_flutter_vlc_player_platform_interface.yaml b/.github/workflows/code_check_for_flutter_vlc_player_platform_interface.yaml new file mode 100644 index 00000000..8c213bab --- /dev/null +++ b/.github/workflows/code_check_for_flutter_vlc_player_platform_interface.yaml @@ -0,0 +1,61 @@ +name: Interface ON Push & PR DO Code check +on: [push, pull_request] + +jobs: + code-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Setup flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - name: Check flutter sdk version + working-directory: ./flutter_vlc_player_platform_interface + run: flutter --version + + - name: Get dependencies + working-directory: ./flutter_vlc_player_platform_interface + run: flutter pub get + + - name: Setup Dart Code Metrics + working-directory: ./flutter_vlc_player_platform_interface + run: dart pub get dart_code_metrics + + - name: Dart Code Metrics + working-directory: ./flutter_vlc_player_platform_interface + run: | + dirs_to_analyze="" + if [ -d lib ]; then dirs_to_analyze+=" lib"; fi + if [ -d test ]; then dirs_to_analyze+=" test"; fi + if [ -d example ]; then dirs_to_analyze+=" example"; fi + if [ dirs_to_analyze != "" ] + then + dart run dart_code_metrics:metrics \ + analyze \ + $dirs_to_analyze \ + --fatal-warnings \ + --fatal-performance \ + --fatal-style + dart run dart_code_metrics:metrics \ + check-unused-files \ + $dirs_to_analyze \ + --fatal-unused + fi + - name: Check formatting + working-directory: ./flutter_vlc_player_platform_interface + run: dart format . --set-exit-if-changed + + - name: Run tests + working-directory: ./flutter_vlc_player_platform_interface + run: | + # run tests if `test` folder exists + if [ -d test ] + then + flutter test -r expanded + else + echo "Tests not found." + fi diff --git a/README.md b/README.md index 2badc4d1..2d59680f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Flutter VLC Player Plugin [![Join the chat at https://discord.gg/mNY4fjVk](https://img.shields.io/discord/716939396464508958?label=discord)](https://discord.gg/mNY4fjVk) [![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dsolidsoftwarehq%26type%3Dpatrons&style=flat)](https://patreon.com/solidsoftwarehq) +[![flutter_vlc_player](https://nokycucwgzweensacwfy.supabase.co/functions/v1/get_project_badge?projectId=148)](https://nokycucwgzweensacwfy.supabase.co/functions/v1/get_project_url?projectId=148) A VLC-powered alternative to Flutter's video_player that supports iOS and Android. @@ -90,7 +91,6 @@ android { buildTypes { release { minifyEnabled true - useProguard true proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' diff --git a/flutter_vlc_player/CHANGELOG.md b/flutter_vlc_player/CHANGELOG.md index 610c2575..55b89de4 100644 --- a/flutter_vlc_player/CHANGELOG.md +++ b/flutter_vlc_player/CHANGELOG.md @@ -1,3 +1,47 @@ +## 7.4.2 +* fixed getVolume #486 + Credits to pinpong (https://github.com/pinpong) +* updated MobileVLCKit & libvlc + Credits to pinpong (https://github.com/pinpong) +* Fix: Unable to replay when status is stopped #449 + Credits to Virczz (https://github.com/Virczz) + +## 7.4.1 +* Add support for Flutter 3.16 + Credits to thearaks (https://github.com/thearaks) + +## 7.4.0 +* Important change: Removed [AutomaticKeepAliveClientMixin](https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html) from plugin widget +* Mobile VLC update to 3.6.0-eap9 +* Allow background playback + Credits to Oliver Nitzschke (https://github.com/pinpong) +* fix instructions for proguard + Credits to Luiz Fernando Baldo Marques (https://github.com/luizbaldo) + +## 7.3.1 +* Restore Flutter 3.3-3.7 compatibility + Credits to Yang Fang (https://github.com/yangsfang) + +## 7.3.0 +* Fix http-user-agent & reuse options on iOS + Credits to Afriza N. Arief (https://github.com/afriza) +* Update to Dart 3 and Flutter 3.13 + Credits to romain.gyh (https://github.com/romaingyh) + +## 7.2.0 +* Update to latest VLCKit sdks +Credits to Mitch Ross (https://github.com/mitchross) + +## 7.1.5 +* Fix plugin destructor (https://github.com/solid-software/flutter_vlc_player/issues/237) + +## 7.1.4 +* Interim release to fix Flutter 3 issues + +## 7.1.3 +* Added support for multi-window mode in Android. +Credits to Andy Chentsov (https://github.com/andyduke). + ## 7.1.2 * Add Hybrid composition support for Android. diff --git a/flutter_vlc_player/README.md b/flutter_vlc_player/README.md index 86c442d6..7133947c 100644 --- a/flutter_vlc_player/README.md +++ b/flutter_vlc_player/README.md @@ -1,4 +1,8 @@ # VLC Player Plugin +[![style: solid](https://img.shields.io/badge/style-solid-orange)](https://pub.dev/packages/solid_lints) +[![flutter_vlc_player](https://nokycucwgzweensacwfy.supabase.co/functions/v1/get_project_badge?projectId=148)](https://nokycucwgzweensacwfy.supabase.co/functions/v1/get_project_url?projectId=148) + + A VLC-powered alternative to Flutter's video_player that supports iOS and Android.
@@ -87,7 +91,6 @@ android { buildTypes { release { minifyEnabled true - useProguard true proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -100,6 +103,25 @@ android { ```proguard -keep class org.videolan.libvlc.** { *; } ``` +
+ +#### Android multi-window support + +To enable multi-window support in your Android application, you need to make changes to `AndroidManifest.xml`, add the `android:resizeableActivity` key for the main activity, as well as the `android.allow_multiple_resumed_activities` metadata for application: +```xml + + + + ... + + ... + + + +```
diff --git a/flutter_vlc_player/analysis_options.yaml b/flutter_vlc_player/analysis_options.yaml index a3be6b82..803a5f78 100644 --- a/flutter_vlc_player/analysis_options.yaml +++ b/flutter_vlc_player/analysis_options.yaml @@ -1 +1,12 @@ -include: package:flutter_lints/flutter.yaml \ No newline at end of file +include: package:solid_lints/analysis_options.yaml + +dart_code_metrics: + metrics: + cyclomatic-complexity: 30 + +linter: + rules: + lines_longer_than_80_chars: false + comment_references: false + public_member_api_docs: false + avoid_positional_boolean_parameters: false diff --git a/flutter_vlc_player/android/build.gradle b/flutter_vlc_player/android/build.gradle index 1a546ce1..70b9b7cc 100644 --- a/flutter_vlc_player/android/build.gradle +++ b/flutter_vlc_player/android/build.gradle @@ -4,28 +4,30 @@ version '1.0-SNAPSHOT' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.4.2' } } rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } apply plugin: 'com.android.library' android { - compileSdkVersion 31 + namespace 'software.solid.fluttervlcplayer' + + compileSdk 34 defaultConfig { - minSdkVersion 20 + minSdkVersion 19 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -39,7 +41,7 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'org.videolan.android:libvlc-all:3.5.0-eap6' + implementation 'org.videolan.android:libvlc-all:3.6.0-eap12' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.annotation:annotation:1.2.0' diff --git a/flutter_vlc_player/android/gradle/wrapper/gradle-wrapper.properties b/flutter_vlc_player/android/gradle/wrapper/gradle-wrapper.properties index 13ab0660..79dd5b14 100644 --- a/flutter_vlc_player/android/gradle/wrapper/gradle-wrapper.properties +++ b/flutter_vlc_player/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-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip diff --git a/flutter_vlc_player/android/src/main/AndroidManifest.xml b/flutter_vlc_player/android/src/main/AndroidManifest.xml index a3fe31b3..a2f47b60 100644 --- a/flutter_vlc_player/android/src/main/AndroidManifest.xml +++ b/flutter_vlc_player/android/src/main/AndroidManifest.xml @@ -1,3 +1,2 @@ - + diff --git a/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayer.java b/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayer.java index c2f69268..c48e0cad 100644 --- a/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayer.java +++ b/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayer.java @@ -6,18 +6,16 @@ import org.videolan.libvlc.RendererDiscoverer; import org.videolan.libvlc.RendererItem; import org.videolan.libvlc.interfaces.IMedia; +import org.videolan.libvlc.interfaces.IVLCVout; import android.content.Context; import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import android.net.Uri; -import android.os.Handler; -import android.os.Looper; import android.util.Base64; import android.util.Log; import android.view.Surface; import android.view.SurfaceView; -import android.view.TextureView; import android.view.View; import io.flutter.plugin.common.BinaryMessenger; @@ -39,7 +37,7 @@ final class FlutterVlcPlayer implements PlatformView { private final boolean debug = false; // private final Context context; - private final TextureView textureView; + private final VLCTextureView textureView; private final TextureRegistry.SurfaceTextureEntry textureEntry; // private final QueuingEventSink mediaEventSink = new QueuingEventSink(); @@ -66,11 +64,13 @@ public void dispose() { if (isDisposed) return; // + textureView.dispose(); textureEntry.release(); mediaEventChannel.setStreamHandler(null); rendererEventChannel.setStreamHandler(null); if (mediaPlayer != null) { mediaPlayer.stop(); + mediaPlayer.setEventListener(null); mediaPlayer.getVLCVout().detachViews(); mediaPlayer.release(); mediaPlayer = null; @@ -115,7 +115,7 @@ public void onCancel(Object o) { }); // textureEntry = textureRegistry.createSurfaceTexture(); - textureView = new TextureView(context); + textureView = new VLCTextureView(context); textureView.setSurfaceTexture(textureEntry.surfaceTexture()); textureView.forceLayout(); textureView.setFitsSystemWindows(true); @@ -126,7 +126,7 @@ public void onCancel(Object o) { // } public void initialize(List options) { - this.options = options; + this.options = options; libVLC = new LibVLC(context, options); mediaPlayer = new MediaPlayer(libVLC); setupVlcMediaPlayer(); @@ -134,81 +134,11 @@ public void initialize(List options) { private void setupVlcMediaPlayer() { - // method 1 - textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { - - boolean wasPlaying = false; - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - log("onSurfaceTextureAvailable"); - - new Handler(Looper.getMainLooper()).postDelayed(() -> { - if (mediaPlayer == null) - return; - mediaPlayer.getVLCVout().setWindowSize(width, height); - mediaPlayer.getVLCVout().setVideoSurface(surface); - if (!mediaPlayer.getVLCVout().areViewsAttached()) - mediaPlayer.getVLCVout().attachViews(); - mediaPlayer.setVideoTrackEnabled(true); - if (wasPlaying) - mediaPlayer.play(); - wasPlaying = false; - }, 100L); - - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - if (mediaPlayer != null) - mediaPlayer.getVLCVout().setWindowSize(width, height); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - log("onSurfaceTextureDestroyed"); - - if (mediaPlayer != null) { - wasPlaying = mediaPlayer.isPlaying(); - mediaPlayer.pause(); - mediaPlayer.setVideoTrackEnabled(false); - mediaPlayer.getVLCVout().detachViews(); - } - return false; //do not return true if you reuse it. - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - - }); - -// method 2 - textureView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - log("onLayoutChange"); - // - if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { - mediaPlayer.pause(); - mediaPlayer.setVideoTrackEnabled(false); - mediaPlayer.getVLCVout().detachViews(); - mediaPlayer.getVLCVout().setWindowSize(view.getWidth(), view.getHeight()); - mediaPlayer.getVLCVout().setVideoView((TextureView) view); - mediaPlayer.getVLCVout().attachViews(); - mediaPlayer.setVideoTrackEnabled(true); - // hacky way to prevent video pixeling, it might be larger than buffer size - long tmpTime = mediaPlayer.getTime() - 500; - if (tmpTime > 0) - mediaPlayer.setTime(tmpTime); - mediaPlayer.play(); - } - } - }); // mediaPlayer.getVLCVout().setWindowSize(textureView.getWidth(), textureView.getHeight()); mediaPlayer.getVLCVout().setVideoSurface(textureView.getSurfaceTexture()); - mediaPlayer.getVLCVout().attachViews(); + textureView.setTextureEntry(textureEntry); + textureView.setMediaPlayer(mediaPlayer); mediaPlayer.setVideoTrackEnabled(true); // mediaPlayer.setEventListener( @@ -313,28 +243,40 @@ public void onEvent(MediaPlayer.Event event) { } void play() { - mediaPlayer.play(); + if (mediaPlayer != null && !mediaPlayer.isPlaying()) { + mediaPlayer.play(); + } } void pause() { - mediaPlayer.pause(); + if (mediaPlayer != null && mediaPlayer.isPlaying()) { + mediaPlayer.pause(); + } } void stop() { - mediaPlayer.stop(); + if (mediaPlayer != null) { + mediaPlayer.stop(); + } } boolean isPlaying() { + if (mediaPlayer == null) return false; return mediaPlayer.isPlaying(); } boolean isSeekable() { + if (mediaPlayer == null) return false; return mediaPlayer.isSeekable(); } void setStreamUrl(String url, boolean isAssetUrl, boolean autoPlay, long hwAcc) { + if (mediaPlayer == null) return; + try { - mediaPlayer.stop(); + if (mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + } // Media media; if (isAssetUrl) @@ -356,15 +298,14 @@ void setStreamUrl(String url, boolean isAssetUrl, boolean autoPlay, long hwAcc) media.addOption(":no-omxil-dr"); } if (options != null) { - for (String option: options) + for (String option : options) media.addOption(option); } mediaPlayer.setMedia(media); media.release(); // - mediaPlayer.play(); - if (!autoPlay) { - mediaPlayer.stop(); + if (autoPlay) { + mediaPlayer.play(); } } catch (IOException e) { log(e.getMessage()); @@ -379,23 +320,33 @@ void setLooping(boolean value) { } void setVolume(long value) { + if (mediaPlayer == null) return; + long bracketedValue = Math.max(0, Math.min(100, value)); mediaPlayer.setVolume((int) bracketedValue); } int getVolume() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getVolume(); } void setPlaybackSpeed(double value) { + if (mediaPlayer == null) return; + mediaPlayer.setRate((float) value); } float getPlaybackSpeed() { + if (mediaPlayer == null) return -1.0f; + return mediaPlayer.getRate(); } void seekTo(int location) { + if (mediaPlayer == null) return; + mediaPlayer.setTime(location); } @@ -404,18 +355,26 @@ void setPosition(float position) { } long getPosition() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getTime(); } long getDuration() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getLength(); } int getSpuTracksCount() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getSpuTracksCount(); } HashMap getSpuTracks() { + if (mediaPlayer == null) return new HashMap(); + MediaPlayer.TrackDescription[] spuTracks = mediaPlayer.getSpuTracks(); HashMap subtitles = new HashMap<>(); if (spuTracks != null) @@ -427,30 +386,44 @@ HashMap getSpuTracks() { } void setSpuTrack(int index) { + if (mediaPlayer == null) return; + mediaPlayer.setSpuTrack(index); } int getSpuTrack() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getSpuTrack(); } void setSpuDelay(long delay) { + if (mediaPlayer == null) return; + mediaPlayer.setSpuDelay(delay); } long getSpuDelay() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getSpuDelay(); } void addSubtitleTrack(String url, boolean isSelected) { + if (mediaPlayer == null) return; + mediaPlayer.addSlave(Media.Slave.Type.Subtitle, Uri.parse(url), isSelected); } int getAudioTracksCount() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getAudioTracksCount(); } HashMap getAudioTracks() { + if (mediaPlayer == null) return new HashMap(); + MediaPlayer.TrackDescription[] audioTracks = mediaPlayer.getAudioTracks(); HashMap audios = new HashMap<>(); if (audioTracks != null) @@ -462,30 +435,44 @@ HashMap getAudioTracks() { } void setAudioTrack(int index) { + if (mediaPlayer == null) return; + mediaPlayer.setAudioTrack(index); } int getAudioTrack() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getAudioTrack(); } void setAudioDelay(long delay) { + if (mediaPlayer == null) return; + mediaPlayer.setAudioDelay(delay); } long getAudioDelay() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getAudioDelay(); } void addAudioTrack(String url, boolean isSelected) { + if (mediaPlayer == null) return; + mediaPlayer.addSlave(Media.Slave.Type.Audio, Uri.parse(url), isSelected); } int getVideoTracksCount() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getVideoTracksCount(); } HashMap getVideoTracks() { + if (mediaPlayer == null) return new HashMap(); + MediaPlayer.TrackDescription[] videoTracks = mediaPlayer.getVideoTracks(); HashMap videos = new HashMap<>(); if (videoTracks != null) @@ -497,30 +484,43 @@ HashMap getVideoTracks() { } void setVideoTrack(int index) { + if (mediaPlayer == null) return; + mediaPlayer.setVideoTrack(index); } int getVideoTrack() { + if (mediaPlayer == null) return -1; + return mediaPlayer.getVideoTrack(); } void setVideoScale(float scale) { + if (mediaPlayer == null) return; + mediaPlayer.setScale(scale); } float getVideoScale() { + if (mediaPlayer == null) return -1.0f; + return mediaPlayer.getScale(); } void setVideoAspectRatio(String aspectRatio) { + if (mediaPlayer == null) return; + mediaPlayer.setAspectRatio(aspectRatio); } String getVideoAspectRatio() { + if (mediaPlayer == null) return ""; + return mediaPlayer.getAspectRatio(); } void startRendererScanning(String rendererService) { + if (libVLC == null) return; // // android -> chromecast -> "microdns" @@ -572,6 +572,8 @@ public void onEvent(RendererDiscoverer.Event event) { } void stopRendererScanning() { + if (mediaPlayer == null) return; + if (isDisposed) return; // @@ -591,6 +593,8 @@ void stopRendererScanning() { } ArrayList getAvailableRendererServices() { + if (libVLC == null) return new ArrayList(); + RendererDiscoverer.Description[] renderers = RendererDiscoverer.list(libVLC); ArrayList availableRendererServices = new ArrayList<>(); for (RendererDiscoverer.Description renderer : renderers) { @@ -609,11 +613,12 @@ HashMap getRendererDevices() { } void castToRenderer(String rendererDevice) { + if (mediaPlayer == null) return; + if (isDisposed) { return; } - boolean isPlaying = mediaPlayer.isPlaying(); - if (isPlaying) + if (mediaPlayer.isPlaying()) mediaPlayer.pause(); // if you set it to null, it will start to render normally (i.e. locally) again @@ -631,6 +636,8 @@ void castToRenderer(String rendererDevice) { } String getSnapshot() { + if (textureView == null) return ""; + Bitmap bitmap = textureView.getBitmap(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); @@ -642,6 +649,7 @@ Boolean startRecording(String directory) { } Boolean stopRecording() { + if (mediaPlayer == null) return true; return mediaPlayer.record(null); } diff --git a/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayerPlugin.java b/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayerPlugin.java index e2f7e944..38cbf87c 100644 --- a/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayerPlugin.java +++ b/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/FlutterVlcPlayerPlugin.java @@ -48,16 +48,8 @@ public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registra @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { flutterPluginBinding = binding; - } - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - flutterPluginBinding = null; - } - - @RequiresApi(api = Build.VERSION_CODES.N) - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + // if (flutterVlcPlayerFactory == null) { final FlutterInjector injector = FlutterInjector.instance(); // @@ -79,20 +71,30 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { startListening(); } + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + stopListening(); + // + + flutterPluginBinding = null; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + } + @Override public void onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity(); } @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - onAttachedToActivity(binding); } @Override public void onDetachedFromActivity() { - stopListening(); } // extra methods @@ -103,7 +105,9 @@ private static void startListening() { } private static void stopListening() { - if (flutterVlcPlayerFactory != null) + if (flutterVlcPlayerFactory != null) { flutterVlcPlayerFactory.stopListening(); + flutterVlcPlayerFactory = null; + } } } diff --git a/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/VLCTextureView.java b/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/VLCTextureView.java new file mode 100644 index 00000000..520aa011 --- /dev/null +++ b/flutter_vlc_player/android/src/main/java/software/solid/fluttervlcplayer/VLCTextureView.java @@ -0,0 +1,213 @@ +package software.solid.fluttervlcplayer; + +import org.videolan.libvlc.MediaPlayer; +import org.videolan.libvlc.interfaces.IVLCVout; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.view.TextureView; +import android.view.View; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import io.flutter.view.TextureRegistry; + +public class VLCTextureView extends TextureView implements TextureView.SurfaceTextureListener, View.OnLayoutChangeListener, IVLCVout.OnNewVideoLayoutListener { + + private MediaPlayer mMediaPlayer = null; + private TextureRegistry.SurfaceTextureEntry mTextureEntry = null; + protected Context mContext; + private SurfaceTexture mSurfaceTexture = null; + private boolean wasPlaying = false; + + private Handler mHandler; + private Runnable mLayoutChangeRunnable = null; + + public VLCTextureView(final Context context) { + super(context); + mContext = context; + initVideoView(); + } + + public VLCTextureView(final Context context, final AttributeSet attrs) { + super(context, attrs); + mContext = context; + initVideoView(); + } + + public VLCTextureView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mContext = context; + initVideoView(); + } + + public void dispose() { + setSurfaceTextureListener(null); + removeOnLayoutChangeListener(this); + + if (mLayoutChangeRunnable != null) { + mHandler.removeCallbacks(mLayoutChangeRunnable); + mLayoutChangeRunnable = null; + } + + if (mSurfaceTexture != null) { + if (!mSurfaceTexture.isReleased()) { + mSurfaceTexture.release(); + } + mSurfaceTexture = null; + } + mTextureEntry = null; + mMediaPlayer = null; + mContext = null; + } + + private void initVideoView() { + mHandler = new Handler(Looper.getMainLooper()); + + setFocusable(false); + setSurfaceTextureListener(this); + addOnLayoutChangeListener(this); + } + + public void setMediaPlayer(MediaPlayer mediaPlayer) { + if (mediaPlayer == null) { + mMediaPlayer.getVLCVout().detachViews(); + } + + mMediaPlayer = mediaPlayer; + + if (mMediaPlayer != null) { + mMediaPlayer.getVLCVout().attachViews(this); + } + } + + public void setTextureEntry(TextureRegistry.SurfaceTextureEntry textureEntry) { + this.mTextureEntry = textureEntry; + this.updateSurfaceTexture(); + } + + private void updateSurfaceTexture() { + if (this.mTextureEntry != null) { + final SurfaceTexture texture = this.mTextureEntry.surfaceTexture(); + if (!texture.isReleased() && (getSurfaceTexture() != texture)) { + setSurfaceTexture(texture); + } + } + } + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + if (mSurfaceTexture == null || mSurfaceTexture.isReleased()) { + mSurfaceTexture = surface; + + if (mMediaPlayer != null) { + mMediaPlayer.getVLCVout().setWindowSize(width, height); + if (!mMediaPlayer.getVLCVout().areViewsAttached()) { + mMediaPlayer.getVLCVout().setVideoSurface(mSurfaceTexture); + if (!mMediaPlayer.getVLCVout().areViewsAttached()) { + mMediaPlayer.getVLCVout().attachViews(this); + } + mMediaPlayer.setVideoTrackEnabled(true); + if (wasPlaying) { + mMediaPlayer.play(); + } + } + } + + wasPlaying = false; + + } else { + if (getSurfaceTexture() != mSurfaceTexture) { + setSurfaceTexture(mSurfaceTexture); + } + } + + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + setSize(width, height); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + if (mMediaPlayer != null) { + wasPlaying = mMediaPlayer.isPlaying(); + } + + if (mSurfaceTexture != surface) { + if (mSurfaceTexture != null) { + if (!mSurfaceTexture.isReleased()) { + mSurfaceTexture.release(); + } + } + mSurfaceTexture = surface; + } + + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + + } + + @Override + public void onNewVideoLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) { + if (width * height == 0) return; + + setSize(width, height); + } + + @Override + public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { + updateLayoutSize(view); + } + } + + public void updateLayoutSize(View view) { + if (mMediaPlayer != null) { + mMediaPlayer.getVLCVout().setWindowSize(view.getWidth(), view.getHeight()); + updateSurfaceTexture(); + } + } + + private void setSize(int width, int height) { + int mVideoWidth = 0; + int mVideoHeight = 0; + mVideoWidth = width; + mVideoHeight = height; + if (mVideoWidth * mVideoHeight <= 1) return; + + // Screen size + int w = this.getWidth(); + int h = this.getHeight(); + + // Size + if (w > h && w < h) { + int i = w; + w = h; + h = i; + } + + float videoAR = (float) mVideoWidth / (float) mVideoHeight; + float screenAR = (float) w / (float) h; + + if (screenAR < videoAR) { + h = (int) (w / videoAR); + } else { + w = (int) (h * videoAR); + } + + // Layout fit + ViewGroup.LayoutParams lp = this.getLayoutParams(); + lp.width = ViewGroup.LayoutParams.MATCH_PARENT; + lp.height = h; + this.setLayoutParams(lp); + this.invalidate(); + } + +} \ No newline at end of file diff --git a/flutter_vlc_player/example/android/app/build.gradle b/flutter_vlc_player/example/android/app/build.gradle index f862e07a..eafb2c4e 100644 --- a/flutter_vlc_player/example/android/app/build.gradle +++ b/flutter_vlc_player/example/android/app/build.gradle @@ -25,7 +25,9 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + namespace 'software.solid.fluttervlcplayerexample' + + compileSdkVersion 34 lintOptions { disable 'InvalidPackage' @@ -33,8 +35,8 @@ android { defaultConfig { applicationId "software.solid.fluttervlcplayerexample" - minSdkVersion 17 - targetSdkVersion 30 + minSdkVersion 23 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -44,6 +46,8 @@ android { release { // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug + minifyEnabled false + shrinkResources false } } } @@ -53,7 +57,7 @@ flutter { } dependencies { - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/flutter_vlc_player/example/android/app/src/main/AndroidManifest.xml b/flutter_vlc_player/example/android/app/src/main/AndroidManifest.xml index e8ae7d35..62cd9ea4 100644 --- a/flutter_vlc_player/example/android/app/src/main/AndroidManifest.xml +++ b/flutter_vlc_player/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - +