├── .gitignore ├── LICENSE ├── docs ├── .nojekyll ├── CNAME ├── _sidebar.md ├── assets │ ├── full.gif │ ├── logo.svg │ ├── pip.gif │ └── q2.gif ├── file-picker.md ├── home.md ├── index.html ├── listening-player-events.md ├── picture-in-picture.md └── variables.json └── plugin ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── app │ └── meedu │ └── player │ ├── MeeduPlayerFlutterActivity.java │ └── MeeduPlayerPlugin.java ├── assets └── icons │ ├── fast-forward.png │ ├── fit.png │ ├── fullscreen.png │ ├── minimize.png │ ├── mute.png │ ├── pause.png │ ├── picture-in-picture.png │ ├── play.png │ ├── repeat.png │ ├── rewind.png │ └── sound.png ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── app │ │ │ │ │ └── meedu │ │ │ │ │ └── player_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── settings_aar.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ └── pages │ │ ├── basic_example_page.dart │ │ ├── change_quality_example_page.dart │ │ ├── custom_icons_example.dart │ │ ├── disabled_buttons_example_page.dart │ │ ├── fullscreen_example_page.dart │ │ ├── listview_example.dart │ │ ├── network_with_subtitle_page.dart │ │ ├── one_page_to_other_page_example.dart │ │ ├── pick_file_page_example.dart │ │ ├── playback_speed_example_page.dart │ │ └── player_with_header_page.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── MeeduPlayerPlugin.h │ ├── MeeduPlayerPlugin.m │ └── SwiftMeeduPlayerPlugin.swift └── meedu_player.podspec ├── lib ├── meedu_player.dart └── src │ ├── controller.dart │ ├── helpers │ ├── custom_icons.dart │ ├── data_source.dart │ ├── enabled_buttons.dart │ ├── meedu_player_status.dart │ ├── player_data_status.dart │ ├── responsive.dart │ ├── screen_manager.dart │ └── utils.dart │ ├── native │ └── pip_manager.dart │ └── widgets │ ├── closed_caption_view.dart │ ├── fullscreen_button.dart │ ├── fullscreen_page.dart │ ├── meedu_video_player.dart │ ├── mute_sound_button.dart │ ├── pip_button.dart │ ├── play_pause_button.dart │ ├── player_button.dart │ ├── player_slider.dart │ ├── styles │ ├── controls_container.dart │ ├── primary │ │ ├── bottom_controls.dart │ │ └── primary_player_controls.dart │ └── secondary │ │ ├── secondary_bottom_controls.dart │ │ └── secondary_player_controls.dart │ └── video_fit_button.dart ├── player.iml ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MEEDU.APP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/docs/.nojekyll -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | player.meedu.app -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Quick Start](/) 2 | - [Picture In Picture](picture-in-picture.md) 3 | - [Listening the player events](listening-player-events.md) 4 | - [File Picker and Android 11](file-picker.md) -------------------------------------------------------------------------------- /docs/assets/full.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/docs/assets/full.gif -------------------------------------------------------------------------------- /docs/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Recurso 5 10 | 11 | 12 | 28 | 35 | 40 | 48 | 49 | 50 | 51 | 57 | 62 | 67 | 68 | -------------------------------------------------------------------------------- /docs/assets/pip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/docs/assets/pip.gif -------------------------------------------------------------------------------- /docs/assets/q2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/docs/assets/q2.gif -------------------------------------------------------------------------------- /docs/file-picker.md: -------------------------------------------------------------------------------- 1 | 2 | If you are using the plugin [file_picker](https://pub.dev/packages/file_picker) maybe you need to add suport for android 11. 3 | 4 | Go to your `build.gradle` in `/android/build.gradle` and update your **build tools** to 4.0.1 or higher 5 | ``` 6 | classpath 'com.android.tools.build:gradle:4.0.1' 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/home.md: -------------------------------------------------------------------------------- 1 | # meedu_player 2 | 3 | 4 | 5 | 6 | 7 | > Modern video player UI for [video_player](https://pub.dev/packages/video_player) 8 | 9 | meedu_player pip 10 | meedu_player 11 |
12 | meedu_player 13 | meedu_player 14 | 15 | 16 |
17 |
18 | 19 | | Features | iOS | Android | 20 | | ------------- | ------------- | ------------- | 21 | | Videos from Network | ✅ | ✅ | 22 | | Videos from Assets | ✅ | ✅ | 23 | | Videos from local files | ✅ | ✅ | 24 | | Looping | ✅ | ✅ | 25 | | Autoplay | ✅ | ✅ | 26 | | Mute / Sound | ✅ | ✅ | 27 | | Fullscreen | ✅ | ✅ | 28 | | Launch Player as Fullscreen | ✅ | ✅ | 29 | | Playback Speed | ✅ | ✅ | 30 | | fastForward / Rewind | ✅ | ✅ | 31 | | srt subtitles | ✅ | ✅ | 32 | | Customize | partially | partially | 33 | | Picture in Picture | ❌ | ✅ | 34 | 35 |
36 | 37 | > For correctly works don't use a device simulator (iOS) or emulator (Android) 38 | 39 | 40 | 41 | 42 | # Add to project 43 | 44 | Add the following to your `pubspec.yaml`: 45 | 46 | ``` 47 | dependencies: 48 | meedu_player: ^${$.var.version} 49 | ``` 50 | 51 | --- 52 | 53 | # Configuration 54 | 55 | > If you want to use urls with `http://` you need a little configuration. 56 | 57 | ## Android 58 | 59 | On Android go to your `/android/app/src/main/AndroidManifest.xml`: 60 | 61 | Add the next permission 62 | `` 63 | 64 | And add `android:usesCleartextTraffic="true"` in your Application tag. 65 |
66 | 67 | ## iOS 68 | 69 | Warning: The video player is not functional on iOS simulators. An iOS device must be used during development/testing. 70 | 71 | Add the following entry to your Info.plist file, located in `/ios/Runner/Info.plist`: 72 | 73 | ```xml 74 | NSAppTransportSecurity 75 | 76 | NSAllowsArbitraryLoads 77 | 78 | 79 | ``` 80 | 81 | In your `Podfile` you need set a minimum target version like 9.0 or higher 82 | 83 | ``` 84 | platform :ios, '9.0' 85 | ``` 86 | 87 | --- 88 | 89 | # How to use 90 | 91 | Fisrt you need to import the `meedu_player` plugin and create an instance of `MeeduPlayerController` 92 | 93 | ```dart 94 | import 'package:meedu_player/meedu_player.dart'; 95 | . 96 | . 97 | . 98 | final _meeduPlayerController = MeeduPlayerController(); 99 | ``` 100 | 101 | > When you create an instance of `MeeduPlayerController` you can pass params like `controlsStyle, colorTheme, placeholder, etc`. 102 | 103 | Now you can use the `MeeduVideoPlayer` widget to show your video, the `MeeduVideoPlayer` widget takes the size of its parent container (we recomend to use an AspectRatio widget) 104 | ```dart 105 | AspectRatio( 106 | aspectRatio: 16 / 9, 107 | child: MeeduVideoPlayer( 108 | controller: _meeduPlayerController, 109 | ), 110 | ), 111 | ``` 112 | 113 | In this moment you only can watch a **black** `Container` that is because you need to set a `DataSource` and pass it to your `MeeduPlayerController` instance. 114 | 115 |
116 | 117 | To play a video from **network** 118 | ```dart 119 | _meeduPlayerController.setDataSource( 120 | DataSource( 121 | type: DataSourceType.network, 122 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 123 | ), 124 | autoplay: true, 125 | ); 126 | ``` 127 | 128 |
129 | 130 | To play a video from **assets** (Make sure that your asset is defined in your `pubspec.yaml`) 131 | ```dart 132 | _meeduPlayerController.setDataSource( 133 | DataSource( 134 | type: DataSourceType.asset, 135 | source: "assets/video/big-buck-bunny-360p.mp4", 136 | ), 137 | autoplay: true, 138 | ); 139 | ``` 140 |
141 | 142 | To play a video from a local **File** 143 | ```dart 144 | _meeduPlayerController.setDataSource( 145 | DataSource( 146 | type: DataSourceType.file, 147 | file: , 148 | ), 149 | autoplay: true, 150 | ); 151 | ``` 152 | 153 | 154 | 155 | > **IMPORTANT:** When you don't need more the player you need to call to `dispose` to release the player 156 | ```dart 157 | _meeduPlayerController.dispose(); 158 | ``` 159 | 160 | --- 161 | 162 | # Basic example 163 | 164 | > We are using [wakelock](https://pub.dev/packages/wakelock) to keep the screen on when the video player is using 165 | 166 | ```dart 167 | import 'package:flutter/material.dart'; 168 | import 'package:meedu_player/meedu_player.dart'; 169 | import 'package:wakelock/wakelock.dart'; 170 | 171 | class BasicExamplePage extends StatefulWidget { 172 | BasicExamplePage({Key? key}) : super(key: key); 173 | 174 | @override 175 | _BasicExamplePageState createState() => _BasicExamplePageState(); 176 | } 177 | 178 | class _BasicExamplePageState extends State { 179 | final _meeduPlayerController = MeeduPlayerController( 180 | controlsStyle: ControlsStyle.primary, 181 | ); 182 | 183 | @override 184 | void initState() { 185 | super.initState(); 186 | // The following line will enable the Android and iOS wakelock. 187 | Wakelock.enable(); 188 | 189 | // Wait until the fisrt render the avoid posible errors when use an context while the view is rendering 190 | WidgetsBinding.instance!.addPostFrameCallback((_) { 191 | _init(); 192 | }); 193 | } 194 | 195 | @override 196 | void dispose() { 197 | // The next line disables the wakelock again. 198 | Wakelock.disable(); 199 | _meeduPlayerController.dispose();// release the video player 200 | super.dispose(); 201 | } 202 | 203 | /// play a video from network 204 | _init() { 205 | _meeduPlayerController.setDataSource( 206 | DataSource( 207 | type: DataSourceType.network, 208 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 209 | ), 210 | autoplay: true, 211 | ); 212 | } 213 | 214 | @override 215 | Widget build(BuildContext context) { 216 | return Scaffold( 217 | appBar: AppBar(), 218 | body: SafeArea( 219 | child: AspectRatio( 220 | aspectRatio: 16 / 9, 221 | child: MeeduVideoPlayer( 222 | controller: _meeduPlayerController, 223 | ), 224 | ), 225 | ), 226 | ); 227 | } 228 | } 229 | ``` 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 11 | 12 | 17 | 18 | 19 | 20 | 24 |
Please wait...
25 | 26 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/listening-player-events.md: -------------------------------------------------------------------------------- 1 | # Listening the player events 2 | 3 | To listen some events like loading, error, playing, paused, finished, etc. You can listen the stream events. 4 | 5 | For example to keep the screen on when the video player is playing a video 6 | 7 | ```dart 8 | StreamSubscription _playerEventSubs; 9 | . 10 | . 11 | . 12 | // in your initState or any other method 13 | _playerEventSubs = _meeduPlayerController.onPlayerStatusChanged.listen( 14 | (PlayerStatus status) { 15 | if (status == PlayerStatus.playing) { 16 | Wakelock.enable();// keep the screen on 17 | } else { // if the video is finished or paused 18 | Wakelock.disable(); 19 | } 20 | }, 21 | ); 22 | . 23 | . 24 | . 25 | // DON'T FORGET CANCEL YOUR SUBSCRIPTIONS 26 | @override 27 | void dispose() { 28 | // The next line disables the wakelock again. 29 | _playerEventSubs?.cancel(); 30 | Wakelock.disable();// if you are using wakelock 31 | _meeduPlayerController.dispose(); 32 | super.dispose(); 33 | } 34 | ``` 35 | 36 | ## Streams 37 | 38 | - `Stream onDataStatusChanged` 39 | Stream to listen changes in the `dataSource` as **none, loading, loaded**, or **error**. 40 | 41 | - `Stream onPlayerStatusChanged` 42 | Stream to listen changes in the player as **playing, paused**, or **stopped**. 43 | 44 | - `Stream onPositionChanged` 45 | Stream to listen the video position 46 | 47 | - `Stream onDurationChanged` 48 | Stream to listen the video duration 49 | 50 | - `Stream onFullscreenChanged` 51 | Stream to listen when the player enters or exits from fullscreen 52 | 53 | - `Stream onShowControlsChanged` 54 | Stream to listen when the controls are visible or hidden 55 | -------------------------------------------------------------------------------- /docs/picture-in-picture.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Picture in Picture 4 | 5 | meedu_player pip 6 | 7 | > Only **Android** is supported for now, pip mode on iOS is supported since iOS 14 but the flutter SDK actually does not have a stable support for iOS 14 8 | 9 | ## Android 10 | To enable the picture in picture mode on Android you need the next requirements. 11 | 12 | First to allow to **meedu_player** listen the picture in picture events your `MainAcvity` class must extends from `MeeduPlayerFlutterActivity` 13 | ```java 14 | import app.meedu.player.MeeduPlayerFlutterActivity; 15 | 16 | public class MainActivity extends MeeduPlayerFlutterActivity { 17 | ... 18 | } 19 | ``` 20 | > The `MeeduPlayerFlutterActivity` class extends from `FlutterActivity` and overrides the `onPictureInPictureModeChanged` method to listen the changes in the pip mode. 21 | 22 | In your `AndroidManifest.xml` in your MainActivity tag you must enable `android:supportsPictureInPicture` and `android:resizeableActivity` 23 | ```xml 24 | **NOTE:** The picture in picture mode is only available since **Android 7** 32 | 33 | 34 | 35 | > When you create your instance of `MeeduPlayerController` you need pass the `pipEnabled` param as **true** and if you want to show the **pip button** in the controls you can pass the `showPipButton` param and then you don't need call to `enterPip` method. 36 | ```dart 37 | final _meeduPlayerController = MeeduPlayerController( 38 | controlsStyle: ControlsStyle.primary, 39 | pipEnabled: true, // use false to hide pip button in the player 40 | showPipButton: true, 41 | ); 42 | ``` 43 | 44 | To enter to the picture in picture mode you can call the `enterPip` method 45 | ```dart 46 | _meeduPlayerController.enterPip(context); 47 | ``` -------------------------------------------------------------------------------- /docs/variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "var": { 3 | "version": "0.5.0-nullsafety.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /plugin/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .dart_tool/ 4 | 5 | .packages 6 | .pub/ 7 | 8 | build/ 9 | -------------------------------------------------------------------------------- /plugin/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.5.0-nullsafety.0] 2 | - Added support for flutter 2 and null safety. 3 | ## [0.4.3] 4 | - Added video fit feature by [Utkarsh Sharma](https://github.com/uttusharma). 5 | 6 | ## [0.4.2] 7 | - Fixed loading animation autoplay false. 8 | 9 | ## [0.4.1+1] 10 | - Removed logs. 11 | ## [0.4.1] 12 | - Added loading view while the video is playing. 13 | - Removed GetX. 14 | - Updated video_player to 1.0.1 15 | 16 | ## [0.4.0+3] 17 | - Updated documentation. 18 | ## [0.4.0+2] 19 | - Fixed broken urls in documentation. 20 | ## [0.4.0+1] 21 | - Fixed broken url in documentation. 22 | ## [0.4.0] 23 | - Added custom icons 24 | - Added hide buttons support 25 | - Updated examples 26 | - Updated to video_player:^1.0.0 27 | - Updated to get:^3.15.0 28 | 29 | ## [0.3.4] 30 | - Updated examples 31 | 32 | ## [0.3.3] 33 | - Removed GetxController 34 | 35 | ## [0.3.2] 36 | - Remove listeners before dispose 37 | 38 | ## [0.3.1] 39 | 40 | - Updated documentation 41 | - Added picture in picture support for Android 42 | 43 | ## [0.3.0] 44 | 45 | - Breaking changes 46 | - Use GetX to manage the video player state 47 | - Added Playback Speed 48 | - Added setLooping 49 | - `MeeduPlayer` has been changed for `MeeduVideoPlayer` 50 | - Removed `MeeduPlayerEventsMixin` now to listen the players events you can use the streams 51 | - Added mutiples controls styles 52 | - Updated the examples 53 | - Added Official documentation 54 | 55 | ## [0.2.3] 56 | 57 | - Fixed rotation after close fullscreen mode 58 | 59 | ## [0.2.2] 60 | 61 | - Added onPlayerPlaying event 62 | - Updated example 63 | 64 | ## [0.2.1] 65 | 66 | - updated doc 67 | 68 | ## [0.2.0] 69 | 70 | - added auto dismiss controls, launch fullscreen as landscape mode 71 | 72 | ## [0.1.2] 73 | 74 | - updated examples 75 | 76 | ## [0.1.1] 77 | 78 | - added fullscreen events listener 79 | 80 | ## [0.1.0+1] 81 | 82 | - added license 83 | 84 | ## [0.1.0] 85 | 86 | - added launch as fullscreen 87 | 88 | ## [0.0.4] 89 | 90 | - removed unnecesary imports 91 | 92 | ## [0.0.3+1] 93 | 94 | - added exmaples 95 | 96 | ## [0.0.3] 97 | 98 | - added suport to subtitles (STR) and switch video resolution 99 | 100 | ## [0.0.2] 101 | 102 | - added Fullscreen support 103 | 104 | ## [0.0.1] - initial version 105 | 106 | - minimal version 107 | -------------------------------------------------------------------------------- /plugin/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MEEDU.APP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /plugin/README.md: -------------------------------------------------------------------------------- 1 | # meedu_player 2 | 3 | 4 | 5 | 6 | 7 | > Modern video player UI for [video_player](https://pub.dev/packages/video_player) 8 | 9 | meedu_player pip 10 | meedu_player 11 |
12 | meedu_player 13 | meedu_player 14 | 15 | | Features | iOS | Android | 16 | | ------------- | ------------- | ------------- | 17 | | Videos from Network | ✅ | ✅ | 18 | | Videos from Assets | ✅ | ✅ | 19 | | Videos from local files | ✅ | ✅ | 20 | | Looping | ✅ | ✅ | 21 | | Autoplay | ✅ | ✅ | 22 | | Mute / Sound | ✅ | ✅ | 23 | | Fullscreen | ✅ | ✅ | 24 | | Launch Player as Fullscreen | ✅ | ✅ | 25 | | Playback Speed | ✅ | ✅ | 26 | | fastForward / Rewind | ✅ | ✅ | 27 | | srt subtitles | ✅ | ✅ | 28 | | Customize | partially | partially | 29 | | Picture in Picture | ❌ | ✅ | 30 | 31 | --- 32 | 33 | 👋 👉 [Complete documentation here](https://player.meedu.app) -------------------------------------------------------------------------------- /plugin/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /plugin/android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'app.meedu.player' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | } 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /plugin/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /plugin/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 6 | -------------------------------------------------------------------------------- /plugin/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /plugin/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /plugin/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'player' 2 | -------------------------------------------------------------------------------- /plugin/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /plugin/android/src/main/java/app/meedu/player/MeeduPlayerFlutterActivity.java: -------------------------------------------------------------------------------- 1 | package app.meedu.player; 2 | 3 | import android.content.res.Configuration; 4 | 5 | import io.flutter.embedding.android.FlutterActivity; 6 | 7 | public class MeeduPlayerFlutterActivity extends FlutterActivity { 8 | 9 | OnPictureInPictureListener onPictureInPictureListener; 10 | 11 | 12 | @Override 13 | public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { 14 | super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); 15 | if (onPictureInPictureListener != null) { 16 | onPictureInPictureListener.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); 17 | } 18 | } 19 | } 20 | 21 | 22 | interface OnPictureInPictureListener { 23 | void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig); 24 | } -------------------------------------------------------------------------------- /plugin/android/src/main/java/app/meedu/player/MeeduPlayerPlugin.java: -------------------------------------------------------------------------------- 1 | package app.meedu.player; 2 | 3 | import android.app.Activity; 4 | import android.content.res.Configuration; 5 | import android.os.Build; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | 10 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 11 | import io.flutter.embedding.engine.plugins.activity.ActivityAware; 12 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 13 | import io.flutter.plugin.common.MethodCall; 14 | import io.flutter.plugin.common.MethodChannel; 15 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 16 | import io.flutter.plugin.common.MethodChannel.Result; 17 | import io.flutter.plugin.common.PluginRegistry.Registrar; 18 | 19 | /** 20 | * PlayerPlugin 21 | */ 22 | public class MeeduPlayerPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware, OnPictureInPictureListener { 23 | /// The MethodChannel that will the communication between Flutter and native Android 24 | /// 25 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 26 | /// when the Flutter Engine is detached from the Activity 27 | private MethodChannel channel; 28 | private Activity activity; 29 | 30 | @Override 31 | public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 32 | channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "app.meedu.player"); 33 | channel.setMethodCallHandler(this); 34 | } 35 | 36 | // This static function is optional and equivalent to onAttachedToEngine. It supports the old 37 | // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting 38 | // plugin registration via this function while apps migrate to use the new Android APIs 39 | // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. 40 | // 41 | // It is encouraged to share logic between onAttachedToEngine and registerWith to keep 42 | // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called 43 | // depending on the user's project. onAttachedToEngine or registerWith must both be defined 44 | // in the same class. 45 | public static void registerWith(Registrar registrar) { 46 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "player"); 47 | channel.setMethodCallHandler(new MeeduPlayerPlugin()); 48 | } 49 | 50 | @Override 51 | public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { 52 | 53 | switch (call.method) { 54 | case "initPipConfiguration": 55 | this.initPipConfiguration(); 56 | result.success(null); 57 | break; 58 | case "osVersion": 59 | result.success(Build.VERSION.RELEASE); 60 | break; 61 | 62 | case "enterPip": 63 | this.enterPipMode(); 64 | result.success(null); 65 | break; 66 | default: 67 | result.notImplemented(); 68 | } 69 | } 70 | 71 | @Override 72 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 73 | channel.setMethodCallHandler(null); 74 | } 75 | 76 | 77 | private void initPipConfiguration() { 78 | ((MeeduPlayerFlutterActivity) this.activity).onPictureInPictureListener = this; 79 | } 80 | 81 | /** 82 | * Start the picture in picture mode 83 | */ 84 | private void enterPipMode() { 85 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 86 | if (activity != null) { 87 | this.activity.enterPictureInPictureMode(); 88 | } 89 | } 90 | } 91 | 92 | @Override 93 | public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { 94 | this.activity = binding.getActivity(); 95 | } 96 | 97 | @Override 98 | public void onDetachedFromActivityForConfigChanges() { 99 | 100 | } 101 | 102 | @Override 103 | public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { 104 | 105 | } 106 | 107 | @Override 108 | public void onDetachedFromActivity() { 109 | 110 | } 111 | 112 | 113 | @Override 114 | public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { 115 | channel.invokeMethod("onPictureInPictureModeChanged", isInPictureInPictureMode); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /plugin/assets/icons/fast-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/fast-forward.png -------------------------------------------------------------------------------- /plugin/assets/icons/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/fit.png -------------------------------------------------------------------------------- /plugin/assets/icons/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/fullscreen.png -------------------------------------------------------------------------------- /plugin/assets/icons/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/minimize.png -------------------------------------------------------------------------------- /plugin/assets/icons/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/mute.png -------------------------------------------------------------------------------- /plugin/assets/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/pause.png -------------------------------------------------------------------------------- /plugin/assets/icons/picture-in-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/picture-in-picture.png -------------------------------------------------------------------------------- /plugin/assets/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/play.png -------------------------------------------------------------------------------- /plugin/assets/icons/repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/repeat.png -------------------------------------------------------------------------------- /plugin/assets/icons/rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/rewind.png -------------------------------------------------------------------------------- /plugin/assets/icons/sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/assets/icons/sound.png -------------------------------------------------------------------------------- /plugin/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /plugin/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /plugin/example/README.md: -------------------------------------------------------------------------------- 1 | # player_example 2 | 3 | Demonstrates how to use the player plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /plugin/example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /plugin/example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "app.meedu.player_example" 37 | minSdkVersion 21 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | } 42 | 43 | buildTypes { 44 | release { 45 | // TODO: Add your own signing config for the release build. 46 | // Signing with the debug keys for now, so `flutter run --release` works. 47 | signingConfig signingConfigs.debug 48 | } 49 | } 50 | } 51 | 52 | flutter { 53 | source '../..' 54 | } 55 | -------------------------------------------------------------------------------- /plugin/example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 14 | 23 | 27 | 31 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/java/app/meedu/player_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package app.meedu.player_example; 2 | 3 | import app.meedu.player.MeeduPlayerFlutterActivity; 4 | 5 | public class MainActivity extends MeeduPlayerFlutterActivity { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /plugin/example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /plugin/example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /plugin/example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.0.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /plugin/example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /plugin/example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /plugin/example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /plugin/example/android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /plugin/example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /plugin/example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /plugin/example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /plugin/example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /plugin/example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /plugin/example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DKImagePickerController/Core (4.3.2): 3 | - DKImagePickerController/ImageDataManager 4 | - DKImagePickerController/Resource 5 | - DKImagePickerController/ImageDataManager (4.3.2) 6 | - DKImagePickerController/PhotoGallery (4.3.2): 7 | - DKImagePickerController/Core 8 | - DKPhotoGallery 9 | - DKImagePickerController/Resource (4.3.2) 10 | - DKPhotoGallery (0.0.17): 11 | - DKPhotoGallery/Core (= 0.0.17) 12 | - DKPhotoGallery/Model (= 0.0.17) 13 | - DKPhotoGallery/Preview (= 0.0.17) 14 | - DKPhotoGallery/Resource (= 0.0.17) 15 | - SDWebImage 16 | - SwiftyGif 17 | - DKPhotoGallery/Core (0.0.17): 18 | - DKPhotoGallery/Model 19 | - DKPhotoGallery/Preview 20 | - SDWebImage 21 | - SwiftyGif 22 | - DKPhotoGallery/Model (0.0.17): 23 | - SDWebImage 24 | - SwiftyGif 25 | - DKPhotoGallery/Preview (0.0.17): 26 | - DKPhotoGallery/Model 27 | - DKPhotoGallery/Resource 28 | - SDWebImage 29 | - SwiftyGif 30 | - DKPhotoGallery/Resource (0.0.17): 31 | - SDWebImage 32 | - SwiftyGif 33 | - file_picker (0.0.1): 34 | - DKImagePickerController/PhotoGallery 35 | - Flutter 36 | - Flutter (1.0.0) 37 | - meedu_player (0.3.4): 38 | - Flutter 39 | - SDWebImage (5.9.5): 40 | - SDWebImage/Core (= 5.9.5) 41 | - SDWebImage/Core (5.9.5) 42 | - SwiftyGif (5.3.0) 43 | - video_player (0.0.1): 44 | - Flutter 45 | - wakelock (0.0.1): 46 | - Flutter 47 | 48 | DEPENDENCIES: 49 | - file_picker (from `.symlinks/plugins/file_picker/ios`) 50 | - Flutter (from `Flutter`) 51 | - meedu_player (from `.symlinks/plugins/meedu_player/ios`) 52 | - video_player (from `.symlinks/plugins/video_player/ios`) 53 | - wakelock (from `.symlinks/plugins/wakelock/ios`) 54 | 55 | SPEC REPOS: 56 | trunk: 57 | - DKImagePickerController 58 | - DKPhotoGallery 59 | - SDWebImage 60 | - SwiftyGif 61 | 62 | EXTERNAL SOURCES: 63 | file_picker: 64 | :path: ".symlinks/plugins/file_picker/ios" 65 | Flutter: 66 | :path: Flutter 67 | meedu_player: 68 | :path: ".symlinks/plugins/meedu_player/ios" 69 | video_player: 70 | :path: ".symlinks/plugins/video_player/ios" 71 | wakelock: 72 | :path: ".symlinks/plugins/wakelock/ios" 73 | 74 | SPEC CHECKSUMS: 75 | DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d 76 | DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 77 | file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 78 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 79 | meedu_player: 443190e5cf1247367384643b594defbffc094b81 80 | SDWebImage: 0b2ba0d56479bf6a45ecddbfd5558bea93150d25 81 | SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 82 | video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e 83 | wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 84 | 85 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 86 | 87 | COCOAPODS: 1.10.0 88 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | player_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /plugin/example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /plugin/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:player_example/pages/basic_example_page.dart'; 3 | import 'package:player_example/pages/change_quality_example_page.dart'; 4 | import 'package:player_example/pages/custom_icons_example.dart'; 5 | import 'package:player_example/pages/disabled_buttons_example_page.dart'; 6 | import 'package:player_example/pages/fullscreen_example_page.dart'; 7 | import 'package:player_example/pages/listview_example.dart'; 8 | import 'package:player_example/pages/network_with_subtitle_page.dart'; 9 | import 'package:player_example/pages/one_page_to_other_page_example.dart'; 10 | import 'package:player_example/pages/pick_file_page_example.dart'; 11 | import 'package:player_example/pages/playback_speed_example_page.dart'; 12 | import 'package:player_example/pages/player_with_header_page.dart'; 13 | 14 | void main() { 15 | runApp(MyApp()); 16 | } 17 | 18 | class MyApp extends StatelessWidget { 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp( 22 | home: HomePage(), 23 | routes: { 24 | "basic": (_) => BasicExamplePage(), 25 | "fullscreen": (_) => FullscreenExamplePage(), 26 | "with-header": (_) => PlayerWithHeaderPage(), 27 | "subtitles": (_) => NetworkWithSubtitlesPage(), 28 | "playback-speed": (_) => PlayBackSpeedExamplePage(), 29 | "quality-change": (_) => ChangeQualityExamplePage(), 30 | "one-page-to-other": (_) => OnePageExample(), 31 | "pick-file": (_) => PickFileExamplePage(), 32 | "custom-icons": (_) => CustomIconsExamplePage(), 33 | "disabled-buttons": (_) => DisabledButtonsExample(), 34 | "listview": (_) => ListViewExample(), 35 | }, 36 | ); 37 | } 38 | } 39 | 40 | class HomePage extends StatefulWidget { 41 | const HomePage({Key? key}) : super(key: key); 42 | 43 | @override 44 | _HomePageState createState() => _HomePageState(); 45 | } 46 | 47 | class _HomePageState extends State { 48 | @override 49 | void initState() { 50 | super.initState(); 51 | // SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | body: ListView( 58 | children: [ 59 | TextButton( 60 | onPressed: () { 61 | Navigator.pushNamed(context, 'basic'); 62 | }, 63 | child: Text("Basic Network example"), 64 | ), 65 | TextButton( 66 | onPressed: () { 67 | Navigator.pushNamed(context, 'fullscreen'); 68 | }, 69 | child: Text("Fullscreen example"), 70 | ), 71 | TextButton( 72 | onPressed: () { 73 | Navigator.pushNamed(context, 'with-header'); 74 | }, 75 | child: Text("With header example"), 76 | ), 77 | TextButton( 78 | onPressed: () { 79 | Navigator.pushNamed(context, 'subtitles'); 80 | }, 81 | child: Text("With subtitles example"), 82 | ), 83 | TextButton( 84 | onPressed: () { 85 | Navigator.pushNamed(context, 'playback-speed'); 86 | }, 87 | child: Text("Playback speed example"), 88 | ), 89 | TextButton( 90 | onPressed: () { 91 | Navigator.pushNamed(context, 'quality-change'); 92 | }, 93 | child: Text("Quality Change example"), 94 | ), 95 | TextButton( 96 | onPressed: () { 97 | Navigator.pushNamed(context, 'one-page-to-other'); 98 | }, 99 | child: Text("One Page to other"), 100 | ), 101 | TextButton( 102 | onPressed: () { 103 | Navigator.pushNamed(context, 'pick-file'); 104 | }, 105 | child: Text("Pick file"), 106 | ), 107 | TextButton( 108 | onPressed: () { 109 | Navigator.pushNamed(context, 'custom-icons'); 110 | }, 111 | child: Text("Custom Icons"), 112 | ), 113 | TextButton( 114 | onPressed: () { 115 | Navigator.pushNamed(context, 'disabled-buttons'); 116 | }, 117 | child: Text("Disabled Buttons"), 118 | ), 119 | TextButton( 120 | onPressed: () { 121 | Navigator.pushNamed(context, 'listview'); 122 | }, 123 | child: Text("ListView"), 124 | ) 125 | ], 126 | ), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/basic_example_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:meedu_player/meedu_player.dart'; 5 | import 'package:wakelock/wakelock.dart'; 6 | 7 | class BasicExamplePage extends StatefulWidget { 8 | BasicExamplePage({Key? key}) : super(key: key); 9 | 10 | @override 11 | _BasicExamplePageState createState() => _BasicExamplePageState(); 12 | } 13 | 14 | class _BasicExamplePageState extends State { 15 | // read the documentation https://the-meedu-app.github.io/flutter-meedu-player/#/picture-in-picture 16 | // to enable the pip (picture in picture) support on Android 17 | final _meeduPlayerController = MeeduPlayerController( 18 | controlsStyle: ControlsStyle.primary, 19 | pipEnabled: true, // enable pip on android 20 | showPipButton: true, // use false to hide pip button in the player 21 | ); 22 | 23 | StreamSubscription? _playerEventSubs; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | // The following line will enable the Android and iOS wakelock. 29 | _playerEventSubs = _meeduPlayerController.onPlayerStatusChanged.listen( 30 | (PlayerStatus status) { 31 | if (status == PlayerStatus.playing) { 32 | Wakelock.enable(); 33 | } else { 34 | Wakelock.disable(); 35 | } 36 | }, 37 | ); 38 | 39 | WidgetsBinding.instance!.addPostFrameCallback((_) { 40 | _init(); 41 | }); 42 | } 43 | 44 | @override 45 | void dispose() { 46 | // The next line disables the wakelock again. 47 | _playerEventSubs?.cancel(); 48 | Wakelock.disable(); 49 | _meeduPlayerController.dispose(); 50 | super.dispose(); 51 | } 52 | 53 | _init() { 54 | _meeduPlayerController.setDataSource( 55 | DataSource( 56 | type: DataSourceType.network, 57 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 58 | ), 59 | autoplay: true, 60 | ); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: AppBar(), 67 | body: SafeArea( 68 | child: AspectRatio( 69 | aspectRatio: 16 / 9, 70 | child: MeeduVideoPlayer( 71 | controller: _meeduPlayerController, 72 | ), 73 | ), 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/change_quality_example_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:meedu_player/meedu_player.dart'; 6 | 7 | class Quality { 8 | final String url, label; 9 | Quality({ 10 | required this.url, 11 | required this.label, 12 | }); 13 | } 14 | 15 | class ChangeQualityExamplePage extends StatefulWidget { 16 | ChangeQualityExamplePage({Key? key}) : super(key: key); 17 | 18 | @override 19 | _ChangeQualityExamplePageState createState() => _ChangeQualityExamplePageState(); 20 | } 21 | 22 | class _ChangeQualityExamplePageState extends State { 23 | final _controller = MeeduPlayerController( 24 | screenManager: ScreenManager( 25 | forceLandScapeInFullscreen: false, 26 | ), 27 | ); 28 | 29 | final _qualities = [ 30 | Quality( 31 | url: 32 | "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h480p.mov", 33 | label: "480p", 34 | ), 35 | Quality( 36 | url: 37 | "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov", 38 | label: "720p", 39 | ), 40 | Quality( 41 | url: 42 | "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h1080p.mov", 43 | label: "1080p", 44 | ), 45 | ]; 46 | 47 | /// listener for the video quality 48 | ValueNotifier _quality = ValueNotifier(null); 49 | 50 | Duration _currentPosition = Duration.zero; // to save the video position 51 | 52 | /// subscription to listen the video position changes 53 | StreamSubscription? _currentPositionSubs; 54 | 55 | @override 56 | void initState() { 57 | super.initState(); 58 | _quality.value = _qualities[0]; // set the default video quality (480p) 59 | 60 | // listen the video position 61 | _currentPositionSubs = _controller.onPositionChanged.listen( 62 | (Duration position) { 63 | _currentPosition = position; // save the video position 64 | }, 65 | ); 66 | WidgetsBinding.instance!.addPostFrameCallback((_) { 67 | _setDataSource(); 68 | }); 69 | } 70 | 71 | @override 72 | void dispose() { 73 | _currentPositionSubs?.cancel(); // cancel the subscription 74 | _controller.dispose(); 75 | super.dispose(); 76 | } 77 | 78 | void _onChangeVideoQuality() { 79 | showCupertinoModalPopup( 80 | context: context, 81 | builder: (_) => CupertinoActionSheet( 82 | actions: List.generate( 83 | _qualities.length, 84 | (index) { 85 | final quality = _qualities[index]; 86 | return CupertinoActionSheetAction( 87 | child: Text("${quality.label}"), 88 | onPressed: () { 89 | _quality.value = quality; // change the video quality 90 | _setDataSource(); // update the datasource 91 | Navigator.pop(_); 92 | }, 93 | ); 94 | }, 95 | ), 96 | cancelButton: CupertinoActionSheetAction( 97 | onPressed: () => Navigator.pop(_), 98 | child: Text("Cancel"), 99 | isDestructiveAction: true, 100 | ), 101 | ), 102 | ); 103 | } 104 | 105 | Future _setDataSource() async { 106 | // set the data source and play the video in the last video position 107 | await _controller.setDataSource( 108 | DataSource( 109 | type: DataSourceType.network, 110 | source: _quality.value!.url, 111 | ), 112 | autoplay: true, 113 | seekTo: _currentPosition, 114 | ); 115 | } 116 | 117 | @override 118 | Widget build(BuildContext context) { 119 | return Scaffold( 120 | appBar: AppBar(), 121 | body: AspectRatio( 122 | aspectRatio: 16 / 9, 123 | child: MeeduVideoPlayer( 124 | controller: _controller, 125 | bottomRight: (ctx, controller, responsive) { 126 | // creates a responsive fontSize using the size of video container 127 | final double fontSize = responsive.ip(3); 128 | 129 | return CupertinoButton( 130 | padding: EdgeInsets.all(5), 131 | minSize: 25, 132 | child: ValueListenableBuilder( 133 | valueListenable: this._quality, 134 | builder: (context, Quality? quality, child) { 135 | return Text( 136 | "${quality!.label}", 137 | style: TextStyle( 138 | fontSize: fontSize > 18 ? 18 : fontSize, 139 | color: Colors.white, 140 | ), 141 | ); 142 | }, 143 | ), 144 | onPressed: _onChangeVideoQuality, 145 | ); 146 | }, 147 | ), 148 | ), 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/custom_icons_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:meedu_player/meedu_player.dart'; 5 | import 'package:wakelock/wakelock.dart'; 6 | 7 | class CustomIconsExamplePage extends StatefulWidget { 8 | CustomIconsExamplePage({Key? key}) : super(key: key); 9 | 10 | @override 11 | _CustomIconsExamplePageState createState() => _CustomIconsExamplePageState(); 12 | } 13 | 14 | class _CustomIconsExamplePageState extends State { 15 | // read the documentation https://the-meedu-app.github.io/flutter-meedu-player/#/picture-in-picture 16 | // to enable the pip (picture in picture) support on Android 17 | final _meeduPlayerController = MeeduPlayerController( 18 | controlsStyle: ControlsStyle.primary, 19 | pipEnabled: true, // enable pip on android 20 | showPipButton: true, // use false to hide pip button in the player 21 | ); 22 | 23 | StreamSubscription? _playerEventSubs; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | // The following line will enable the Android and iOS wakelock. 29 | _playerEventSubs = _meeduPlayerController.onPlayerStatusChanged.listen( 30 | (PlayerStatus status) { 31 | if (status == PlayerStatus.playing) { 32 | Wakelock.enable(); 33 | } else { 34 | Wakelock.disable(); 35 | } 36 | }, 37 | ); 38 | 39 | WidgetsBinding.instance!.addPostFrameCallback((_) { 40 | _init(); 41 | }); 42 | } 43 | 44 | @override 45 | void dispose() { 46 | // The next line disables the wakelock again. 47 | _playerEventSubs?.cancel(); 48 | Wakelock.disable(); 49 | _meeduPlayerController.dispose(); 50 | super.dispose(); 51 | } 52 | 53 | _init() { 54 | _meeduPlayerController.setDataSource( 55 | DataSource( 56 | type: DataSourceType.network, 57 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 58 | ), 59 | autoplay: true, 60 | ); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: AppBar(), 67 | body: SafeArea( 68 | child: AspectRatio( 69 | aspectRatio: 16 / 9, 70 | child: MeeduVideoPlayer( 71 | controller: _meeduPlayerController, 72 | customIcons: (responsive) { 73 | final iconSize = responsive.ip(15); 74 | final miniIconSize = responsive.ip(7); 75 | return CustomIcons( 76 | play: Container( 77 | padding: EdgeInsets.all(iconSize * 0.2), 78 | child: Icon( 79 | Icons.play_arrow, 80 | size: iconSize, 81 | color: Colors.redAccent, 82 | ), 83 | ), 84 | pause: Container( 85 | padding: EdgeInsets.all(iconSize * 0.2), 86 | child: Icon( 87 | Icons.pause, 88 | size: iconSize, 89 | color: Colors.redAccent, 90 | ), 91 | ), 92 | rewind: Container( 93 | padding: EdgeInsets.all(iconSize * 0.1), 94 | child: Icon( 95 | Icons.fast_rewind_rounded, 96 | size: iconSize * 0.8, 97 | color: Colors.redAccent, 98 | ), 99 | ), 100 | fastForward: Container( 101 | padding: EdgeInsets.all(iconSize * 0.1), 102 | child: Icon( 103 | Icons.fast_forward_rounded, 104 | size: iconSize * 0.8, 105 | color: Colors.redAccent, 106 | ), 107 | ), 108 | mute: Container( 109 | padding: EdgeInsets.all(miniIconSize * 0.2), 110 | child: Icon( 111 | Icons.volume_off_rounded, 112 | size: miniIconSize, 113 | color: Colors.redAccent, 114 | ), 115 | ), 116 | sound: Container( 117 | padding: EdgeInsets.all(miniIconSize * 0.2), 118 | child: Icon( 119 | Icons.volume_up_rounded, 120 | size: miniIconSize, 121 | color: Colors.redAccent, 122 | ), 123 | ), 124 | fullscreen: Container( 125 | padding: EdgeInsets.all(miniIconSize * 0.2), 126 | child: Icon( 127 | Icons.fullscreen_rounded, 128 | size: miniIconSize, 129 | color: Colors.redAccent, 130 | ), 131 | ), 132 | minimize: Container( 133 | padding: EdgeInsets.all(miniIconSize * 0.2), 134 | child: Icon( 135 | Icons.fullscreen_exit_rounded, 136 | size: miniIconSize, 137 | color: Colors.redAccent, 138 | ), 139 | ), 140 | pip: Container( 141 | padding: EdgeInsets.all(miniIconSize * 0.2), 142 | child: Icon( 143 | Icons.picture_in_picture_alt_rounded, 144 | size: miniIconSize * 0.8, 145 | color: Colors.redAccent, 146 | ), 147 | ), 148 | ); 149 | }, 150 | ), 151 | ), 152 | ), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/disabled_buttons_example_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:meedu_player/meedu_player.dart'; 5 | import 'package:wakelock/wakelock.dart'; 6 | 7 | class DisabledButtonsExample extends StatefulWidget { 8 | DisabledButtonsExample({Key? key}) : super(key: key); 9 | 10 | @override 11 | _DisabledButtonsExampleState createState() => _DisabledButtonsExampleState(); 12 | } 13 | 14 | class _DisabledButtonsExampleState extends State { 15 | // read the documentation https://the-meedu-app.github.io/flutter-meedu-player/#/picture-in-picture 16 | // to enable the pip (picture in picture) support on Android 17 | final _meeduPlayerController = MeeduPlayerController( 18 | controlsStyle: ControlsStyle.primary, 19 | pipEnabled: true, // enable pip on android 20 | showPipButton: true, // use false to hide pip button in the player 21 | enabledButtons: EnabledButtons( 22 | fullscreen: false, 23 | pip: false, 24 | ), 25 | ); 26 | 27 | StreamSubscription? _playerEventSubs; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | // The following line will enable the Android and iOS wakelock. 33 | _playerEventSubs = _meeduPlayerController.onPlayerStatusChanged.listen( 34 | (PlayerStatus status) { 35 | if (status == PlayerStatus.playing) { 36 | Wakelock.enable(); 37 | } else { 38 | Wakelock.disable(); 39 | } 40 | }, 41 | ); 42 | 43 | WidgetsBinding.instance!.addPostFrameCallback((_) { 44 | _init(); 45 | }); 46 | } 47 | 48 | @override 49 | void dispose() { 50 | // The next line disables the wakelock again. 51 | _playerEventSubs?.cancel(); 52 | Wakelock.disable(); 53 | _meeduPlayerController.dispose(); 54 | super.dispose(); 55 | } 56 | 57 | _init() { 58 | _meeduPlayerController.setDataSource( 59 | DataSource( 60 | type: DataSourceType.network, 61 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 62 | ), 63 | autoplay: true, 64 | ); 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return Scaffold( 70 | appBar: AppBar(), 71 | body: SafeArea( 72 | child: AspectRatio( 73 | aspectRatio: 16 / 9, 74 | child: MeeduVideoPlayer( 75 | controller: _meeduPlayerController, 76 | ), 77 | ), 78 | ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/fullscreen_example_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:meedu_player/meedu_player.dart'; 6 | 7 | const videos = [ 8 | 'https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov', 9 | 'https://movietrailers.apple.com/movies/independent/bill-ted-face-the-music/bill-and-ted-face-the-music-trailer-1_h720p.mov', 10 | 'https://movietrailers.apple.com/movies/roadsideattractions/words-on-bathroom-walls/words-on-bathroom-walls-trailer-1_h720p.mov', 11 | 'https://movietrailers.apple.com/movies/independent/alone/alone-trailer-1_h720p.mov', 12 | 'https://movietrailers.apple.com/movies/fox/the-new-mutants/the-new-mutants-trailer-1_h720p.mov', 13 | ]; 14 | 15 | class FullscreenExamplePage extends StatefulWidget { 16 | FullscreenExamplePage({Key? key}) : super(key: key); 17 | 18 | @override 19 | _FullscreenExamplePageState createState() => _FullscreenExamplePageState(); 20 | } 21 | 22 | class _FullscreenExamplePageState extends State { 23 | // final MeeduPlayerController _meeduPlayerController = MeeduPlayerController( 24 | // screenManager: ScreenManager( 25 | // forceLandScapeInFullscreen: false, 26 | // orientations: [ 27 | // DeviceOrientation.landscapeLeft, 28 | // DeviceOrientation.landscapeRight, 29 | // DeviceOrientation.portraitDown, 30 | // DeviceOrientation.portraitUp, 31 | // ], 32 | // ), 33 | // ); 34 | 35 | final MeeduPlayerController _meeduPlayerController = MeeduPlayerController( 36 | colorTheme: Colors.blue, 37 | ); 38 | ValueNotifier currentIndex = ValueNotifier(0); 39 | DataSource? _dataSource; 40 | 41 | StreamSubscription? _subscription; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | _subscription = _meeduPlayerController.onFullscreenChanged.listen( 47 | (bool isFullscreen) { 48 | if (!isFullscreen) { 49 | // if the fullscreen page was closed 50 | _dataSource = null; 51 | } 52 | }, 53 | ); 54 | } 55 | 56 | @override 57 | void dispose() { 58 | _subscription?.cancel(); 59 | _meeduPlayerController.dispose(); 60 | super.dispose(); 61 | } 62 | 63 | Widget get nextButton { 64 | return ValueListenableBuilder( 65 | valueListenable: currentIndex, 66 | builder: (_, int index, __) { 67 | final hasNext = index < videos.length - 1; 68 | return TextButton( 69 | onPressed: hasNext 70 | ? () { 71 | currentIndex.value++; 72 | this._set(); 73 | } 74 | : null, 75 | child: Text( 76 | "NEXT VIDEO", 77 | style: TextStyle( 78 | color: Colors.white.withOpacity(hasNext ? 1 : 0.2), 79 | ), 80 | ), 81 | ); 82 | }, 83 | ); 84 | } 85 | 86 | Widget get header { 87 | return ValueListenableBuilder( 88 | valueListenable: currentIndex, 89 | builder: (_, int index, __) { 90 | return Container( 91 | padding: EdgeInsets.all(10), 92 | child: Row( 93 | children: [ 94 | CupertinoButton( 95 | child: Icon( 96 | Icons.arrow_back, 97 | color: Colors.white, 98 | ), 99 | onPressed: () { 100 | // close the fullscreen 101 | Navigator.pop(context); 102 | }, 103 | ), 104 | Expanded( 105 | child: Text( 106 | videos[index], 107 | style: TextStyle( 108 | color: Colors.white, 109 | ), 110 | ), 111 | ), 112 | ], 113 | ), 114 | ); 115 | }, 116 | ); 117 | } 118 | 119 | Future _set() async { 120 | final index = currentIndex.value; 121 | if (_dataSource == null) { 122 | // if the player is not launched yet 123 | _dataSource = DataSource( 124 | source: videos[index], 125 | type: DataSourceType.network, 126 | ); 127 | 128 | // launch the player in fullscreen mode 129 | await this._meeduPlayerController.launchAsFullscreen( 130 | context, 131 | dataSource: _dataSource!, 132 | autoplay: true, 133 | header: header, 134 | bottomRight: nextButton, 135 | ); 136 | } else { 137 | // update the player with new datasource and it doesn't re-launch the player 138 | await this._meeduPlayerController.setDataSource( 139 | _dataSource!.copyWith( 140 | source: videos[index], 141 | ), 142 | seekTo: Duration.zero, 143 | ); 144 | } 145 | } 146 | 147 | @override 148 | Widget build(BuildContext context) { 149 | return Scaffold( 150 | appBar: AppBar(), 151 | body: ListView( 152 | children: List.generate( 153 | videos.length, 154 | (index) => ListTile( 155 | onTap: () { 156 | currentIndex.value = index; 157 | this._set(); 158 | }, 159 | title: Text("video ${index + 1}"), 160 | ), 161 | ), 162 | ), 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/listview_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:visibility_detector/visibility_detector.dart'; 5 | 6 | class ListViewExample extends StatefulWidget { 7 | ListViewExample({Key? key}) : super(key: key); 8 | 9 | @override 10 | _ListViewExampleState createState() => _ListViewExampleState(); 11 | } 12 | 13 | class _ListViewExampleState extends State with AutomaticKeepAliveClientMixin { 14 | @override 15 | Widget build(BuildContext context) { 16 | super.build(context); 17 | return Scaffold( 18 | appBar: AppBar(), 19 | body: ListView.builder( 20 | itemBuilder: (_, index) => VideoItem( 21 | uniqueKey: "$index", 22 | ), 23 | itemCount: 50, 24 | ), 25 | ); 26 | } 27 | 28 | @override 29 | bool get wantKeepAlive => true; 30 | } 31 | 32 | class VideoItem extends StatefulWidget { 33 | final String uniqueKey; 34 | VideoItem({Key? key, required this.uniqueKey}) : super(key: key); 35 | 36 | @override 37 | _VideoItemState createState() => _VideoItemState(); 38 | } 39 | 40 | class _VideoItemState extends State with AutomaticKeepAliveClientMixin { 41 | MeeduPlayerController _controller = MeeduPlayerController( 42 | screenManager: ScreenManager(orientations: [ 43 | DeviceOrientation.portraitUp, 44 | ]), 45 | ); 46 | 47 | ValueNotifier _visible = ValueNotifier(true); 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | _controller.setDataSource( 53 | DataSource( 54 | source: 'https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4', 55 | type: DataSourceType.network, 56 | ), 57 | autoplay: false, 58 | ); 59 | } 60 | 61 | @override 62 | void dispose() { 63 | _controller.dispose(); 64 | 65 | print("❌ dispose video player"); 66 | super.dispose(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | super.build(context); 72 | return VisibilityDetector( 73 | key: Key(widget.uniqueKey), 74 | onVisibilityChanged: (info) { 75 | final visible = info.visibleFraction > 0; 76 | if (_visible.value != visible) { 77 | _visible.value = visible; 78 | if (!visible && _controller.videoPlayerController!.value.isPlaying) { 79 | _controller.pause(); 80 | } 81 | } 82 | }, 83 | child: AspectRatio( 84 | aspectRatio: 16 / 9, 85 | child: ValueListenableBuilder( 86 | valueListenable: _visible, 87 | builder: (_, visible, child) { 88 | return visible 89 | ? MeeduVideoPlayer( 90 | controller: _controller, 91 | ) 92 | : child!; 93 | }, 94 | child: Container(), 95 | ), 96 | ), 97 | ); 98 | } 99 | 100 | @override 101 | bool get wantKeepAlive => true; 102 | } 103 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/network_with_subtitle_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | 5 | class NetworkWithSubtitlesPage extends StatefulWidget { 6 | @override 7 | _NetworkWithSubtitlesPageState createState() => _NetworkWithSubtitlesPageState(); 8 | } 9 | 10 | class _NetworkWithSubtitlesPageState extends State { 11 | late MeeduPlayerController _controller; 12 | 13 | ValueNotifier _subtitlesEnabled = ValueNotifier(true); 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | _controller = MeeduPlayerController( 19 | controlsStyle: ControlsStyle.primary, 20 | ); 21 | this._setDataSource(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | this._controller.dispose(); 27 | super.dispose(); 28 | } 29 | 30 | _setDataSource() async { 31 | await _controller.setDataSource( 32 | DataSource( 33 | source: 'https://thepaciellogroup.github.io/AT-browser-tests/video/ElephantsDream.mp4', 34 | type: DataSourceType.network, 35 | closedCaptionFile: this._loadCaptions(), 36 | ), 37 | autoplay: true, 38 | ); 39 | _controller.onClosedCaptionEnabled(true); 40 | } 41 | 42 | Future _loadCaptions() async { 43 | // you can get the fileContents as string from a remote str file using a http client like dio or http 44 | // or you can load from assets 45 | /* 46 | final String fileContents = await DefaultAssetBundle.of(context) 47 | .loadString('assets/captions.srt'); 48 | */ 49 | // in srt format 50 | final String fileContents = ''' 51 | 0 52 | 00:00:02,170 --> 00:00:04,136 53 | Emo, close your eyes 54 | 55 | 1 56 | 00:00:04,136 --> 00:00:05,597 57 | Why? 58 | NOW! 59 | 60 | 2 61 | 00:00:05,597 --> 00:00:07,405 62 | Ok 63 | 64 | 3 65 | 00:00:07,405 --> 00:00:08,803 66 | Good 67 | 68 | 4 69 | 00:00:08,803 --> 00:00:11,541 70 | What do you see at your left side Emo? 71 | 72 | 5 73 | 00:00:11,541 --> 00:00:13,287 74 | Well? 75 | 76 | 6 77 | 00:00:13,287 --> 00:00:16,110 78 | Er nothing? 79 | Really? 80 | 81 | 7 82 | 00:00:16,110 --> 00:00:18,514 83 | No, nothing at all! 84 | 85 | 8 86 | 00:00:18,514 --> 00:00:22,669 87 | Really? and at your right? What do you see at your right side Emo? 88 | 89 | 9 90 | 00:00:22,669 --> 00:00:26,111 91 | Umm, the same Proog 92 | 93 | 10 94 | 00:00:26,111 --> 00:00:28,646 95 | Exactly the same! Nothing! 96 | 97 | 11 98 | 00:00:28,646 --> 00:00:30,794 99 | Great 100 | '''; 101 | return SubRipCaptionFile(fileContents); 102 | } 103 | 104 | @override 105 | Widget build(BuildContext context) { 106 | return Scaffold( 107 | appBar: AppBar(), 108 | body: SafeArea( 109 | child: AspectRatio( 110 | aspectRatio: 16 / 9, 111 | child: MeeduVideoPlayer( 112 | controller: this._controller, 113 | bottomRight: (ctx, controller, responsive) { 114 | // creates a responsive fontSize using the size of video container 115 | final double fontSize = responsive.ip(3); 116 | 117 | return CupertinoButton( 118 | padding: EdgeInsets.all(5), 119 | minSize: 25, 120 | child: ValueListenableBuilder( 121 | valueListenable: this._subtitlesEnabled, 122 | builder: (BuildContext context, bool enabled, _) { 123 | return Text( 124 | "CC", 125 | style: TextStyle( 126 | fontSize: fontSize > 18 ? 18 : fontSize, 127 | color: Colors.white.withOpacity( 128 | enabled ? 1 : 0.4, 129 | ), 130 | ), 131 | ); 132 | }, 133 | ), 134 | onPressed: () { 135 | _subtitlesEnabled.value = !_subtitlesEnabled.value; 136 | this._controller.onClosedCaptionEnabled(_subtitlesEnabled.value); 137 | }, 138 | ); 139 | }, 140 | ), 141 | ), 142 | ), 143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/one_page_to_other_page_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:meedu_player/meedu_player.dart'; 5 | import 'package:wakelock/wakelock.dart'; 6 | 7 | class OnePageExample extends StatefulWidget { 8 | @override 9 | _OnePageExampleState createState() => _OnePageExampleState(); 10 | } 11 | 12 | class _OnePageExampleState extends State { 13 | // read the documentation https://the-meedu-app.github.io/flutter-meedu-player/#/picture-in-picture 14 | // to enable the pip (picture in picture) support on Android 15 | MeeduPlayerController? _meeduPlayerController = MeeduPlayerController( 16 | controlsStyle: ControlsStyle.secondary, 17 | pipEnabled: true, // enable pip on android 18 | showPipButton: true, // use false to hide pip button in the player 19 | ); 20 | 21 | StreamSubscription? _playerEventSubs; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | // The following line will enable the Android and iOS wakelock. 27 | _playerEventSubs = _meeduPlayerController!.onPlayerStatusChanged.listen( 28 | (PlayerStatus status) { 29 | if (status == PlayerStatus.playing) { 30 | Wakelock.enable(); 31 | } else { 32 | Wakelock.disable(); 33 | } 34 | }, 35 | ); 36 | 37 | WidgetsBinding.instance!.addPostFrameCallback((_) { 38 | _init(); 39 | }); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | _meeduDispose(); 45 | super.dispose(); 46 | } 47 | 48 | Future _meeduDispose() async { 49 | if (_meeduPlayerController != null) { 50 | _playerEventSubs?.cancel(); 51 | await _meeduPlayerController!.dispose(); 52 | _meeduPlayerController = null; 53 | // The next line disables the wakelock again. 54 | await Wakelock.disable(); 55 | } 56 | } 57 | 58 | _init() { 59 | _meeduPlayerController!.setDataSource( 60 | DataSource( 61 | type: DataSourceType.network, 62 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 63 | ), 64 | autoplay: true, 65 | ); 66 | } 67 | 68 | Future _gotTo() async { 69 | final route = MaterialPageRoute( 70 | builder: (_) => PageTwo(), 71 | ); 72 | Navigator.pushReplacement(context, route); 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Scaffold( 78 | appBar: AppBar( 79 | title: Text("Page 1"), 80 | ), 81 | body: SafeArea( 82 | child: Column( 83 | children: [ 84 | AspectRatio( 85 | aspectRatio: 16 / 9, 86 | child: MeeduVideoPlayer( 87 | controller: _meeduPlayerController!, 88 | ), 89 | ), 90 | SizedBox(height: 2), 91 | TextButton( 92 | onPressed: this._gotTo, 93 | child: Text("Page 2"), 94 | ), 95 | ], 96 | ), 97 | ), 98 | ); 99 | } 100 | } 101 | 102 | class PageTwo extends StatefulWidget { 103 | PageTwo({Key? key}) : super(key: key); 104 | 105 | @override 106 | _PageTwoState createState() => _PageTwoState(); 107 | } 108 | 109 | class _PageTwoState extends State { 110 | // read the documentation https://the-meedu-app.github.io/flutter-meedu-player/#/picture-in-picture 111 | // to enable the pip (picture in picture) support on Android 112 | final _meeduPlayerController = MeeduPlayerController( 113 | controlsStyle: ControlsStyle.primary, 114 | pipEnabled: true, // enable pip on android 115 | showPipButton: true, // use false to hide pip button in the player 116 | ); 117 | 118 | StreamSubscription? _playerEventSubs; 119 | 120 | @override 121 | void initState() { 122 | super.initState(); 123 | // The following line will enable the Android and iOS wakelock. 124 | _playerEventSubs = _meeduPlayerController.onPlayerStatusChanged.listen( 125 | (PlayerStatus status) { 126 | if (status == PlayerStatus.playing) { 127 | Wakelock.enable(); 128 | } else { 129 | Wakelock.disable(); 130 | } 131 | }, 132 | ); 133 | 134 | WidgetsBinding.instance!.addPostFrameCallback((_) { 135 | _init(); 136 | }); 137 | } 138 | 139 | @override 140 | void dispose() { 141 | // The next line disables the wakelock again. 142 | _playerEventSubs?.cancel(); 143 | Wakelock.disable(); 144 | _meeduPlayerController.dispose(); 145 | super.dispose(); 146 | } 147 | 148 | _init() { 149 | _meeduPlayerController.setDataSource( 150 | DataSource( 151 | type: DataSourceType.network, 152 | source: "https://movietrailers.apple.com/movies/fox/the-new-mutants/the-new-mutants-trailer-1_h720p.mov", 153 | ), 154 | autoplay: true, 155 | ); 156 | } 157 | 158 | @override 159 | Widget build(BuildContext context) { 160 | return Scaffold( 161 | appBar: AppBar( 162 | title: Text("Page 2"), 163 | ), 164 | body: SafeArea( 165 | child: AspectRatio( 166 | aspectRatio: 16 / 9, 167 | child: MeeduVideoPlayer( 168 | controller: _meeduPlayerController, 169 | ), 170 | ), 171 | ), 172 | ); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/pick_file_page_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:file_picker/file_picker.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:meedu_player/meedu_player.dart'; 5 | 6 | class PickFileExamplePage extends StatefulWidget { 7 | PickFileExamplePage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _PickFileExamplePageState createState() => _PickFileExamplePageState(); 11 | } 12 | 13 | class _PickFileExamplePageState extends State { 14 | final _controller = MeeduPlayerController( 15 | screenManager: ScreenManager(forceLandScapeInFullscreen: false), 16 | ); 17 | 18 | @override 19 | void dispose() { 20 | _controller.dispose(); 21 | super.dispose(); 22 | } 23 | 24 | _onPickFile() async { 25 | FilePickerResult? result = await FilePicker.platform.pickFiles( 26 | type: FileType.custom, 27 | allowedExtensions: ['mov', 'avi', 'mp4'], 28 | ); 29 | 30 | if (result != null) { 31 | File file = File(result.files.single.path!); 32 | _controller.launchAsFullscreen( 33 | context, 34 | autoplay: true, 35 | dataSource: DataSource( 36 | file: file, 37 | type: DataSourceType.file, 38 | ), 39 | ); 40 | } else { 41 | // User canceled the picker 42 | } 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Scaffold( 48 | body: Center( 49 | child: TextButton( 50 | child: Text("Pick video file"), 51 | onPressed: this._onPickFile, 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/playback_speed_example_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | 5 | class PlayBackSpeedExamplePage extends StatefulWidget { 6 | PlayBackSpeedExamplePage({Key? key}) : super(key: key); 7 | 8 | @override 9 | _PlayBackSpeedExamplePageState createState() => _PlayBackSpeedExamplePageState(); 10 | } 11 | 12 | class _PlayBackSpeedExamplePageState extends State { 13 | final _controller = MeeduPlayerController( 14 | screenManager: ScreenManager( 15 | forceLandScapeInFullscreen: false, 16 | ), 17 | ); 18 | 19 | ValueNotifier _playbackSpeed = ValueNotifier(1); 20 | 21 | void _onPlaybackSpeed() { 22 | final options = [0.2, 0.5, 1.0, 2.0, 4.0]; 23 | showCupertinoModalPopup( 24 | context: context, 25 | builder: (_) => CupertinoActionSheet( 26 | actions: List.generate( 27 | options.length, 28 | (index) => CupertinoActionSheetAction( 29 | child: Text("${options[index]}x"), 30 | onPressed: () { 31 | _playbackSpeed.value = options[index]; 32 | // change the playback speed 33 | _controller.setPlaybackSpeed( 34 | _playbackSpeed.value, 35 | ); 36 | // hide the modal 37 | Navigator.pop(_); 38 | }, 39 | ), 40 | ), 41 | cancelButton: CupertinoActionSheetAction( 42 | onPressed: () => Navigator.pop(_), 43 | child: Text("Cancel"), 44 | isDestructiveAction: true, 45 | ), 46 | ), 47 | ); 48 | } 49 | 50 | @override 51 | void initState() { 52 | super.initState(); 53 | WidgetsBinding.instance!.addPostFrameCallback((_) { 54 | _init(); 55 | }); 56 | } 57 | 58 | @override 59 | void dispose() { 60 | _controller.dispose(); 61 | super.dispose(); 62 | } 63 | 64 | _init() { 65 | _controller.setDataSource( 66 | DataSource( 67 | type: DataSourceType.network, 68 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 69 | ), 70 | autoplay: true, 71 | ); 72 | } 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return Scaffold( 77 | appBar: AppBar(), 78 | body: AspectRatio( 79 | aspectRatio: 16 / 9, 80 | child: MeeduVideoPlayer( 81 | controller: _controller, 82 | bottomRight: (ctx, controller, responsive) { 83 | // creates a responsive fontSize using the size of video container 84 | final double fontSize = responsive.ip(3); 85 | 86 | return CupertinoButton( 87 | padding: EdgeInsets.all(5), 88 | minSize: 25, 89 | child: ValueListenableBuilder( 90 | valueListenable: this._playbackSpeed, 91 | builder: (context, double speed, child) { 92 | return Text( 93 | "$speed x", 94 | style: TextStyle( 95 | fontSize: fontSize > 18 ? 18 : fontSize, 96 | color: Colors.white, 97 | ), 98 | ); 99 | }, 100 | ), 101 | onPressed: _onPlaybackSpeed, 102 | ); 103 | }, 104 | ), 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /plugin/example/lib/pages/player_with_header_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:wakelock/wakelock.dart'; 5 | 6 | class PlayerWithHeaderPage extends StatefulWidget { 7 | PlayerWithHeaderPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _PlayerWithHeaderPageState createState() => _PlayerWithHeaderPageState(); 11 | } 12 | 13 | class _PlayerWithHeaderPageState extends State { 14 | MeeduPlayerController _meeduPlayerController = MeeduPlayerController( 15 | controlsStyle: ControlsStyle.secondary, 16 | ); 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | 22 | // The following line will enable the Android and iOS wakelock. 23 | Wakelock.enable(); 24 | WidgetsBinding.instance!.addPostFrameCallback((_) { 25 | _init(); 26 | }); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | // The next line disables the wakelock again. 32 | Wakelock.disable(); 33 | _meeduPlayerController.dispose(); 34 | super.dispose(); 35 | } 36 | 37 | _init() { 38 | _meeduPlayerController.setDataSource( 39 | DataSource( 40 | type: DataSourceType.network, 41 | source: "https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4", 42 | ), 43 | autoplay: true, 44 | ); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Scaffold( 50 | appBar: AppBar(), 51 | body: SafeArea( 52 | child: AspectRatio( 53 | aspectRatio: 16 / 9, 54 | child: MeeduVideoPlayer( 55 | header: (ctx, controller, responsive) { 56 | // creates a responsive fontSize using the size of video container 57 | final double fontSize = responsive.ip(3); 58 | 59 | return Container( 60 | padding: EdgeInsets.only(left: 10), 61 | color: Colors.black12, 62 | child: Row( 63 | crossAxisAlignment: CrossAxisAlignment.center, 64 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 65 | children: [ 66 | Text( 67 | "Insert you title 3", 68 | style: TextStyle( 69 | color: Colors.white, 70 | fontSize: fontSize > 17 ? 17 : fontSize, 71 | ), 72 | ), 73 | CupertinoButton( 74 | padding: EdgeInsets.all(5), 75 | child: Icon( 76 | CupertinoIcons.share, 77 | color: Colors.white, 78 | ), 79 | onPressed: () {}, 80 | ) 81 | ], 82 | ), 83 | ); 84 | }, 85 | controller: _meeduPlayerController, 86 | ), 87 | ), 88 | ), 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /plugin/example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.2" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.0" 60 | ffi: 61 | dependency: transitive 62 | description: 63 | name: ffi 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.0" 67 | file_picker: 68 | dependency: "direct main" 69 | description: 70 | name: file_picker 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "3.0.0" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_meedu: 80 | dependency: transitive 81 | description: 82 | name: flutter_meedu 83 | url: "https://pub.dartlang.org" 84 | source: hosted 85 | version: "2.0.1" 86 | flutter_plugin_android_lifecycle: 87 | dependency: transitive 88 | description: 89 | name: flutter_plugin_android_lifecycle 90 | url: "https://pub.dartlang.org" 91 | source: hosted 92 | version: "2.0.0" 93 | flutter_spinkit: 94 | dependency: transitive 95 | description: 96 | name: flutter_spinkit 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "5.0.0" 100 | flutter_test: 101 | dependency: "direct dev" 102 | description: flutter 103 | source: sdk 104 | version: "0.0.0" 105 | flutter_web_plugins: 106 | dependency: transitive 107 | description: flutter 108 | source: sdk 109 | version: "0.0.0" 110 | js: 111 | dependency: transitive 112 | description: 113 | name: js 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "0.6.3" 117 | matcher: 118 | dependency: transitive 119 | description: 120 | name: matcher 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "0.12.10" 124 | meedu: 125 | dependency: transitive 126 | description: 127 | name: meedu 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "2.0.1" 131 | meedu_player: 132 | dependency: "direct dev" 133 | description: 134 | path: ".." 135 | relative: true 136 | source: path 137 | version: "0.5.0-nullsafety.0" 138 | meta: 139 | dependency: transitive 140 | description: 141 | name: meta 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.3.0" 145 | nested: 146 | dependency: transitive 147 | description: 148 | name: nested 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.0.0" 152 | path: 153 | dependency: transitive 154 | description: 155 | name: path 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.8.0" 159 | plugin_platform_interface: 160 | dependency: transitive 161 | description: 162 | name: plugin_platform_interface 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.0" 166 | provider: 167 | dependency: transitive 168 | description: 169 | name: provider 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "5.0.0" 173 | sky_engine: 174 | dependency: transitive 175 | description: flutter 176 | source: sdk 177 | version: "0.0.99" 178 | source_span: 179 | dependency: transitive 180 | description: 181 | name: source_span 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "1.8.0" 185 | stack_trace: 186 | dependency: transitive 187 | description: 188 | name: stack_trace 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "1.10.0" 192 | stream_channel: 193 | dependency: transitive 194 | description: 195 | name: stream_channel 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "2.1.0" 199 | string_scanner: 200 | dependency: transitive 201 | description: 202 | name: string_scanner 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "1.1.0" 206 | term_glyph: 207 | dependency: transitive 208 | description: 209 | name: term_glyph 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "1.2.0" 213 | test_api: 214 | dependency: transitive 215 | description: 216 | name: test_api 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "0.2.19" 220 | typed_data: 221 | dependency: transitive 222 | description: 223 | name: typed_data 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "1.3.0" 227 | vector_math: 228 | dependency: transitive 229 | description: 230 | name: vector_math 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "2.1.0" 234 | video_player: 235 | dependency: transitive 236 | description: 237 | name: video_player 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "2.0.0" 241 | video_player_platform_interface: 242 | dependency: transitive 243 | description: 244 | name: video_player_platform_interface 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "4.0.0" 248 | video_player_web: 249 | dependency: transitive 250 | description: 251 | name: video_player_web 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "2.0.0" 255 | visibility_detector: 256 | dependency: "direct main" 257 | description: 258 | name: visibility_detector 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "0.2.0-nullsafety.1" 262 | wakelock: 263 | dependency: "direct main" 264 | description: 265 | name: wakelock 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "0.5.0+2" 269 | wakelock_macos: 270 | dependency: transitive 271 | description: 272 | name: wakelock_macos 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "0.1.0" 276 | wakelock_platform_interface: 277 | dependency: transitive 278 | description: 279 | name: wakelock_platform_interface 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "0.2.0" 283 | wakelock_web: 284 | dependency: transitive 285 | description: 286 | name: wakelock_web 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "0.2.0" 290 | wakelock_windows: 291 | dependency: transitive 292 | description: 293 | name: wakelock_windows 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "0.1.0" 297 | win32: 298 | dependency: transitive 299 | description: 300 | name: win32 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "2.0.0" 304 | sdks: 305 | dart: ">=2.12.0 <3.0.0" 306 | flutter: ">=2.0.0" 307 | -------------------------------------------------------------------------------- /plugin/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: player_example 2 | description: Demonstrates how to use the player plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | wakelock: ^0.5.0+2 15 | file_picker: ^3.0.0 16 | 17 | # The following adds the Cupertino Icons font to your application. 18 | # Use with the CupertinoIcons class for iOS style icons. 19 | cupertino_icons: ^1.0.2 20 | visibility_detector: ^0.2.0-nullsafety.1 21 | 22 | 23 | dev_dependencies: 24 | flutter_test: 25 | sdk: flutter 26 | meedu_player: 27 | path: ../ 28 | 29 | # For information on the generic Dart part of this file, see the 30 | # following page: https://dart.dev/tools/pub/pubspec 31 | 32 | # The following section is specific to Flutter. 33 | flutter: 34 | 35 | # The following line ensures that the Material Icons font is 36 | # included with your application, so that you can use the icons in 37 | # the material Icons class. 38 | uses-material-design: true 39 | 40 | # To add assets to your application, add an assets section, like this: 41 | # assets: 42 | # - images/a_dot_burr.jpeg 43 | # - images/a_dot_ham.jpeg 44 | 45 | # An image asset can refer to one or more resolution-specific "variants", see 46 | # https://flutter.dev/assets-and-images/#resolution-aware. 47 | 48 | # For details regarding adding assets from package dependencies, see 49 | # https://flutter.dev/assets-and-images/#from-packages 50 | 51 | # To add custom fonts to your application, add a fonts section here, 52 | # in this "flutter" section. Each entry in this list should have a 53 | # "family" key with the font family name, and a "fonts" key with a 54 | # list giving the asset and other descriptors for the font. For 55 | # example: 56 | # fonts: 57 | # - family: Schyler 58 | # fonts: 59 | # - asset: fonts/Schyler-Regular.ttf 60 | # - asset: fonts/Schyler-Italic.ttf 61 | # style: italic 62 | # - family: Trajan Pro 63 | # fonts: 64 | # - asset: fonts/TrajanPro.ttf 65 | # - asset: fonts/TrajanPro_Bold.ttf 66 | # weight: 700 67 | # 68 | # For details regarding fonts from package dependencies, 69 | # see https://flutter.dev/custom-fonts/#from-packages 70 | -------------------------------------------------------------------------------- /plugin/example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | void main() {} 9 | -------------------------------------------------------------------------------- /plugin/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /plugin/ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darwin-morocho/flutter-meedu-player/007d2f536693718a05d6a5caac17cc70d23d2c4b/plugin/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /plugin/ios/Classes/MeeduPlayerPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface MeeduPlayerPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /plugin/ios/Classes/MeeduPlayerPlugin.m: -------------------------------------------------------------------------------- 1 | #import "MeeduPlayerPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "meedu_player-Swift.h" 9 | #endif 10 | 11 | @implementation MeeduPlayerPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftMeeduPlayerPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /plugin/ios/Classes/SwiftMeeduPlayerPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftMeeduPlayerPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "meedu_player", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftMeeduPlayerPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | result("iOS " + UIDevice.current.systemVersion) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plugin/ios/meedu_player.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint player.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'meedu_player' 7 | s.version = '0.3.4' 8 | s.summary = 'A new Flutter UI video controls for the flutter video_player plugin.' 9 | s.description = <<-DESC 10 | A new Flutter UI video controls for the flutter video_player plugin.. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '9.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /plugin/lib/meedu_player.dart: -------------------------------------------------------------------------------- 1 | export 'src/controller.dart'; 2 | export 'src/widgets/meedu_video_player.dart'; 3 | export 'src/helpers/data_source.dart'; 4 | export 'src/helpers/player_data_status.dart'; 5 | export 'src/helpers/meedu_player_status.dart'; 6 | export 'src/helpers/screen_manager.dart'; 7 | export 'src/helpers/custom_icons.dart'; 8 | export 'src/helpers/enabled_buttons.dart'; 9 | export 'package:video_player/video_player.dart'; 10 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/custom_icons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// this class help you to change the default player icons 4 | class CustomIcons { 5 | final Widget? play, 6 | pause, 7 | repeat, 8 | rewind, 9 | fastForward, 10 | sound, 11 | mute, 12 | videoFit, 13 | pip, 14 | minimize, 15 | fullscreen; 16 | 17 | const CustomIcons({ 18 | this.play, 19 | this.pause, 20 | this.repeat, 21 | this.rewind, 22 | this.fastForward, 23 | this.sound, 24 | this.mute, 25 | this.videoFit, 26 | this.pip, 27 | this.minimize, 28 | this.fullscreen, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/data_source.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:video_player/video_player.dart' 3 | show VideoFormat, DataSourceType, ClosedCaptionFile; 4 | 5 | class DataSource { 6 | final File? file; 7 | final String? source, package; 8 | final DataSourceType type; 9 | final VideoFormat? formatHint; 10 | final Future? closedCaptionFile; // for subtiles 11 | 12 | DataSource({ 13 | this.file, 14 | this.source, 15 | required this.type, 16 | this.formatHint, 17 | this.package, 18 | this.closedCaptionFile, 19 | }) : assert((type == DataSourceType.file && file != null) || source != null); 20 | 21 | DataSource copyWith({ 22 | File? file, 23 | String? source, 24 | String? package, 25 | DataSourceType? type, 26 | VideoFormat? formatHint, 27 | Future? closedCaptionFile, 28 | }) { 29 | return DataSource( 30 | file: file ?? this.file, 31 | source: source ?? this.source, 32 | type: type ?? this.type, 33 | package: package ?? this.package, 34 | formatHint: formatHint ?? this.formatHint, 35 | closedCaptionFile: closedCaptionFile ?? this.closedCaptionFile, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/enabled_buttons.dart: -------------------------------------------------------------------------------- 1 | /// this class helps you to hide some player buttons 2 | class EnabledButtons { 3 | final bool playPauseAndRepeat, 4 | rewindAndfastForward, 5 | videoFit, 6 | muteAndSound, 7 | pip, 8 | fullscreen; 9 | 10 | const EnabledButtons({ 11 | this.playPauseAndRepeat = true, 12 | this.rewindAndfastForward = true, 13 | this.videoFit = true, 14 | this.muteAndSound = true, 15 | this.pip = true, 16 | this.fullscreen = true, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/meedu_player_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:meedu/rx.dart'; 2 | 3 | enum PlayerStatus { stopped, playing, paused } 4 | 5 | class MeeduPlayerStatus { 6 | Rx status = Rx(PlayerStatus.paused); 7 | 8 | bool get playing { 9 | return status.value == PlayerStatus.playing; 10 | } 11 | 12 | bool get paused { 13 | return status.value == PlayerStatus.paused; 14 | } 15 | 16 | bool get stopped { 17 | return status.value == PlayerStatus.stopped; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/player_data_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:meedu/rx.dart'; 2 | 3 | enum DataStatus { none, loading, loaded, error } 4 | 5 | class MeeduPlayerDataStatus { 6 | Rx status = Rx(DataStatus.none); 7 | 8 | bool get none => status.value == DataStatus.none; 9 | bool get loading => status.value == DataStatus.loading; 10 | bool get loaded => status.value == DataStatus.loaded; 11 | bool get error => status.value == DataStatus.error; 12 | } 13 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/responsive.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | class Responsive { 4 | final double width, height; 5 | late double inch; 6 | Responsive(this.width, this.height) { 7 | inch = math.sqrt((width * width) + (height * height)); 8 | } 9 | 10 | double ip(double percent) { 11 | return inch * percent / 100; 12 | } 13 | 14 | double wp(double percent) { 15 | return width * percent / 100; 16 | } 17 | 18 | double hp(double percent) { 19 | return height * percent / 100; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/screen_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | class ScreenManager { 4 | /// [orientations] the device orientation after exit of the fullscreen 5 | final List orientations; 6 | 7 | /// [overlays] the device overlays after exit of the fullscreen 8 | final List overlays; 9 | 10 | /// when the player is in fullscreen mode if forceLandScapeInFullscreen the player only show the landscape mode 11 | final bool forceLandScapeInFullscreen; 12 | 13 | const ScreenManager({ 14 | this.orientations = DeviceOrientation.values, 15 | this.overlays = SystemUiOverlay.values, 16 | this.forceLandScapeInFullscreen = true, 17 | }); 18 | 19 | /// set the default orientations and overlays after exit of fullscreen 20 | Future setDefaultOverlaysAndOrientations() async { 21 | await SystemChrome.setPreferredOrientations(this.orientations); 22 | await SystemChrome.setEnabledSystemUIOverlays(this.overlays); 23 | } 24 | 25 | /// hide the statusBar and the navigation bar, set only landscape mode only if forceLandScapeInFullscreen is true 26 | Future setFullScreenOverlaysAndOrientations({ 27 | hideOverLays = true, 28 | }) async { 29 | await SystemChrome.setPreferredOrientations(this.forceLandScapeInFullscreen 30 | ? [ 31 | DeviceOrientation.landscapeLeft, 32 | DeviceOrientation.landscapeRight, 33 | ] 34 | : this.orientations); 35 | 36 | if (hideOverLays) { 37 | await SystemChrome.setEnabledSystemUIOverlays([]); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/lib/src/helpers/utils.dart: -------------------------------------------------------------------------------- 1 | String printDuration(Duration? duration) { 2 | if (duration == null) return "--:--"; 3 | 4 | String twoDigits(int n) { 5 | if (n >= 10) return "$n"; 6 | return "0$n"; 7 | } 8 | 9 | String twoDigitMinutes = twoDigits(duration.inMinutes); 10 | String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); 11 | return "$twoDigitMinutes:$twoDigitSeconds"; 12 | } 13 | 14 | String printDurationWithHours(Duration? duration) { 15 | if (duration == null) return "--:--:--"; 16 | 17 | String twoDigits(int n) { 18 | if (n >= 10) return "$n"; 19 | return "0$n"; 20 | } 21 | 22 | String twoDigitHours = twoDigits(duration.inHours); 23 | String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); 24 | String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); 25 | return "$twoDigitHours:$twoDigitMinutes:$twoDigitSeconds"; 26 | } 27 | -------------------------------------------------------------------------------- /plugin/lib/src/native/pip_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' show Platform; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:meedu/rx.dart'; 6 | 7 | class PipManager { 8 | final _channel = MethodChannel("app.meedu.player"); 9 | 10 | Completer _osVersion = Completer(); 11 | Completer _pipAvailable = Completer(); 12 | 13 | Rx isInPipMode = false.obs; 14 | 15 | PipManager() { 16 | _channel.setMethodCallHandler((call) async { 17 | if (call.method == 'onPictureInPictureModeChanged') { 18 | isInPipMode.value = call.arguments; 19 | } 20 | }); 21 | } 22 | 23 | Future get osVersion async { 24 | return _osVersion.future; 25 | } 26 | 27 | Future get pipAvailable async { 28 | return _pipAvailable.future; 29 | } 30 | 31 | Future _getOSVersion() async { 32 | final _os = await _channel.invokeMethod('osVersion'); 33 | final os = double.parse(_os!); 34 | this._osVersion.complete(os); 35 | } 36 | 37 | Future enterPip() async { 38 | await _channel.invokeMethod('enterPip'); 39 | } 40 | 41 | Future checkPipAvailable() async { 42 | bool available = false; 43 | if (Platform.isAndroid) { 44 | await this._channel.invokeMethod('initPipConfiguration'); 45 | await _getOSVersion(); 46 | final osVersion = await _osVersion.future; 47 | // check the OS version 48 | if (osVersion >= 7) { 49 | return true; 50 | } 51 | } 52 | this._pipAvailable.complete(available); 53 | return available; 54 | } 55 | 56 | Future dispose() { 57 | return isInPipMode.close(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/closed_caption_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | 4 | import 'package:meedu_player/meedu_player.dart'; 5 | import 'package:meedu_player/src/helpers/responsive.dart'; 6 | 7 | class ClosedCaptionView extends StatelessWidget { 8 | final Responsive responsive; 9 | const ClosedCaptionView({Key? key, required this.responsive}) 10 | : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final _ = MeeduPlayerController.of(context); 15 | return RxBuilder( 16 | observables: [_.closedCaptionEnabled], 17 | builder: (__) { 18 | if (!_.closedCaptionEnabled.value) return Container(); 19 | 20 | return StreamBuilder( 21 | initialData: Duration.zero, 22 | stream: _.onPositionChanged, 23 | builder: (__, snapshot) { 24 | if (snapshot.hasError) { 25 | return Container(); 26 | } 27 | 28 | final strSubtitle = _.videoPlayerController!.value.caption.text; 29 | 30 | return Positioned( 31 | left: 60, 32 | right: 60, 33 | bottom: 0, 34 | child: ClosedCaption( 35 | text: strSubtitle, 36 | textStyle: TextStyle( 37 | color: Colors.white, 38 | fontSize: responsive.ip(2), 39 | ), 40 | ), 41 | ); 42 | }, 43 | ); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/fullscreen_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | 5 | import 'player_button.dart'; 6 | 7 | class FullscreenButton extends StatelessWidget { 8 | final double size; 9 | const FullscreenButton({Key? key, this.size = 30}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final _ = MeeduPlayerController.of(context); 14 | return RxBuilder( 15 | observables: [_.fullscreen], 16 | builder: (__) { 17 | String iconPath = 'assets/icons/minimize.png'; 18 | Widget? customIcon = _.customIcons.minimize; 19 | 20 | if (!_.fullscreen.value) { 21 | iconPath = 'assets/icons/fullscreen.png'; 22 | customIcon = _.customIcons.fullscreen; 23 | } 24 | return PlayerButton( 25 | size: size, 26 | circle: false, 27 | backgrounColor: Colors.transparent, 28 | iconColor: Colors.white, 29 | iconPath: iconPath, 30 | customIcon: customIcon, 31 | onPressed: () { 32 | if (_.fullscreen.value) { 33 | // exit to fullscreen 34 | Navigator.pop(context); 35 | } else { 36 | _.goToFullscreen(context); 37 | } 38 | }, 39 | ); 40 | }, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/fullscreen_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | 5 | class MeeduPlayerFullscreenPage extends StatelessWidget { 6 | final MeeduPlayerController controller; 7 | 8 | const MeeduPlayerFullscreenPage({Key? key, required this.controller}) 9 | : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | final _size = MediaQuery.of(context).size; 13 | return Scaffold( 14 | backgroundColor: Colors.black, 15 | body: RxBuilder( 16 | observables: [controller.videoFit], 17 | builder: (__) { 18 | return Container( 19 | width: double.infinity, 20 | height: double.infinity, 21 | child: FittedBox( 22 | fit: controller.videoFit.value, 23 | child: SizedBox( 24 | width: _size.width, 25 | height: _size.height, 26 | child: MeeduVideoPlayer( 27 | controller: this.controller, 28 | ), 29 | ), 30 | ), 31 | ); 32 | }, 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/meedu_video_player.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/controller.dart'; 5 | import 'package:meedu_player/src/helpers/responsive.dart'; 6 | import 'package:meedu_player/src/widgets/closed_caption_view.dart'; 7 | import 'package:meedu_player/src/widgets/styles/primary/primary_player_controls.dart'; 8 | import 'package:meedu_player/src/widgets/styles/secondary/secondary_player_controls.dart'; 9 | import 'package:video_player/video_player.dart'; 10 | 11 | class MeeduVideoPlayer extends StatefulWidget { 12 | final MeeduPlayerController controller; 13 | 14 | final Widget Function( 15 | BuildContext context, 16 | MeeduPlayerController controller, 17 | Responsive responsive, 18 | )? header; 19 | 20 | final Widget Function( 21 | BuildContext context, 22 | MeeduPlayerController controller, 23 | Responsive responsive, 24 | )? bottomRight; 25 | 26 | final CustomIcons Function( 27 | Responsive responsive, 28 | )? customIcons; 29 | 30 | MeeduVideoPlayer({ 31 | Key? key, 32 | required this.controller, 33 | this.header, 34 | this.bottomRight, 35 | this.customIcons, 36 | }) : super(key: key); 37 | 38 | @override 39 | _MeeduVideoPlayerState createState() => _MeeduVideoPlayerState(); 40 | } 41 | 42 | class _MeeduVideoPlayerState extends State { 43 | Widget _getView(MeeduPlayerController _) { 44 | if (_.dataStatus.none) return Container(); 45 | if (_.dataStatus.loading) { 46 | return Center( 47 | child: _.loadingWidget, 48 | ); 49 | } 50 | if (_.dataStatus.error) { 51 | return Center( 52 | child: Text( 53 | _.errorText ?? 'Error', 54 | style: TextStyle(color: Colors.white), 55 | ), 56 | ); 57 | } 58 | 59 | return LayoutBuilder( 60 | builder: (ctx, constraints) { 61 | final responsive = Responsive( 62 | constraints.maxWidth, 63 | constraints.maxHeight, 64 | ); 65 | 66 | if (widget.customIcons != null) { 67 | _.customIcons = this.widget.customIcons!(responsive); 68 | } 69 | 70 | if (widget.header != null) { 71 | _.header = this.widget.header!(context, _, responsive); 72 | } 73 | 74 | if (widget.bottomRight != null) { 75 | _.bottomRight = this.widget.bottomRight!(context, _, responsive); 76 | } 77 | return Stack( 78 | alignment: Alignment.center, 79 | children: [ 80 | RxBuilder( 81 | observables: [_.videoFit], 82 | builder: (__) { 83 | return SizedBox.expand( 84 | child: FittedBox( 85 | fit: widget.controller.videoFit.value, 86 | child: SizedBox( 87 | width: _.videoPlayerController!.value.size.width, 88 | height: _.videoPlayerController!.value.size.height, 89 | child: VideoPlayer(_.videoPlayerController!), 90 | ), 91 | ), 92 | ); 93 | }), 94 | ClosedCaptionView(responsive: responsive), 95 | if (_.controlsEnabled && _.controlsStyle == ControlsStyle.primary) 96 | PrimaryVideoPlayerControls( 97 | responsive: responsive, 98 | ), 99 | if (_.controlsEnabled && _.controlsStyle == ControlsStyle.secondary) 100 | SecondaryVideoPlayerControls( 101 | responsive: responsive, 102 | ), 103 | ], 104 | ); 105 | }, 106 | ); 107 | } 108 | 109 | @override 110 | Widget build(BuildContext context) { 111 | return MeeduPlayerProvider( 112 | child: Container( 113 | color: Colors.black, 114 | width: 0.0, 115 | height: 0.0, 116 | child: RxBuilder( 117 | observables: [ 118 | widget.controller.showControls, 119 | widget.controller.dataStatus.status 120 | ], 121 | builder: (__) => _getView(widget.controller), 122 | ), 123 | ), 124 | controller: widget.controller, 125 | ); 126 | } 127 | } 128 | 129 | class MeeduPlayerProvider extends InheritedWidget { 130 | final MeeduPlayerController controller; 131 | 132 | MeeduPlayerProvider({ 133 | Key? key, 134 | required Widget child, 135 | required this.controller, 136 | }) : super(key: key, child: child); 137 | 138 | @override 139 | bool updateShouldNotify(covariant InheritedWidget oldWidget) { 140 | return false; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/mute_sound_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/helpers/responsive.dart'; 5 | 6 | import 'player_button.dart'; 7 | 8 | class MuteSoundButton extends StatelessWidget { 9 | final Responsive responsive; 10 | const MuteSoundButton({Key? key, required this.responsive}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final _ = MeeduPlayerController.of(context); 15 | return RxBuilder( 16 | observables: [_.mute, _.fullscreen], 17 | builder: (__) { 18 | String iconPath = 'assets/icons/mute.png'; 19 | Widget? customIcon = _.customIcons.mute; 20 | 21 | if (!_.mute.value) { 22 | iconPath = 'assets/icons/sound.png'; 23 | customIcon = _.customIcons.sound; 24 | } 25 | 26 | return PlayerButton( 27 | size: responsive.ip(_.fullscreen.value ? 5 : 7), 28 | circle: false, 29 | backgrounColor: Colors.transparent, 30 | iconColor: Colors.white, 31 | iconPath: iconPath, 32 | customIcon: customIcon, 33 | onPressed: () { 34 | _.setMute(!_.mute.value); 35 | }, 36 | ); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/pip_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/helpers/responsive.dart'; 5 | 6 | import 'player_button.dart'; 7 | 8 | class PipButton extends StatelessWidget { 9 | final Responsive responsive; 10 | const PipButton({Key? key, required this.responsive}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final _ = MeeduPlayerController.of(context); 15 | return RxBuilder( 16 | observables: [ 17 | _.pipAvailable, 18 | _.fullscreen, 19 | ], 20 | builder: (__) { 21 | if (!_.pipAvailable.value || !_.showPipButton) return Container(); 22 | return PlayerButton( 23 | size: responsive.ip(_.fullscreen.value ? 5 : 7), 24 | circle: false, 25 | backgrounColor: Colors.transparent, 26 | iconColor: Colors.white, 27 | iconPath: 'assets/icons/picture-in-picture.png', 28 | customIcon: _.customIcons.pip, 29 | onPressed: () => _.enterPip(context), 30 | ); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/play_pause_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_meedu/rx.dart'; 4 | import 'package:meedu_player/meedu_player.dart'; 5 | 6 | import 'player_button.dart'; 7 | 8 | class PlayPauseButton extends StatelessWidget { 9 | final double size; 10 | const PlayPauseButton({Key? key, this.size = 40}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final _ = MeeduPlayerController.of(context); 15 | return RxBuilder( 16 | observables: [ 17 | _.playerStatus.status, 18 | _.buffered, 19 | _.isBuffering, 20 | _.position 21 | ], 22 | builder: (__) { 23 | if (_.isBuffering.value) { 24 | return CupertinoButton(child: _.loadingWidget!, onPressed: _.pause); 25 | } 26 | 27 | String iconPath = 'assets/icons/repeat.png'; 28 | Widget? customIcon = _.customIcons.repeat; 29 | if (_.playerStatus.playing) { 30 | iconPath = 'assets/icons/pause.png'; 31 | customIcon = _.customIcons.pause; 32 | } else if (_.playerStatus.paused) { 33 | iconPath = 'assets/icons/play.png'; 34 | customIcon = _.customIcons.play; 35 | } 36 | return PlayerButton( 37 | backgrounColor: Colors.transparent, 38 | iconColor: Colors.white, 39 | onPressed: () { 40 | if (_.playerStatus.playing) { 41 | _.pause(); 42 | } else if (_.playerStatus.paused) { 43 | _.play(); 44 | } else { 45 | _.play(repeat: true); 46 | } 47 | }, 48 | size: size, 49 | iconPath: iconPath, 50 | customIcon: customIcon, 51 | ); 52 | }, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/player_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class PlayerButton extends StatelessWidget { 5 | final double size; 6 | final String iconPath; 7 | final VoidCallback onPressed; 8 | final Color backgrounColor, iconColor; 9 | final bool circle; 10 | final Widget? customIcon; 11 | 12 | const PlayerButton({ 13 | Key? key, 14 | this.size = 40, 15 | required this.iconPath, 16 | required this.onPressed, 17 | this.circle = true, 18 | this.backgrounColor = Colors.white54, 19 | this.iconColor = Colors.black, 20 | this.customIcon, 21 | }) : super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return CupertinoButton( 26 | padding: EdgeInsets.zero, 27 | minSize: 20, 28 | child: customIcon ?? 29 | Container( 30 | width: this.size, 31 | height: this.size, 32 | padding: EdgeInsets.all(this.size * 0.25), 33 | child: Image.asset( 34 | this.iconPath, 35 | color: this.iconColor, 36 | package: 'meedu_player', 37 | ), 38 | decoration: BoxDecoration( 39 | color: this.backgrounColor, 40 | shape: this.circle ? BoxShape.circle : BoxShape.rectangle, 41 | ), 42 | ), 43 | onPressed: this.onPressed, 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/player_slider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/helpers/utils.dart'; 5 | 6 | class PlayerSlider extends StatelessWidget { 7 | const PlayerSlider({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final _ = MeeduPlayerController.of(context); 12 | return Stack( 13 | alignment: Alignment.centerLeft, 14 | children: [ 15 | Container( 16 | child: LayoutBuilder(builder: (ctx, constraints) { 17 | return RxBuilder( 18 | observables: [_.buffered, _.duration], 19 | builder: (__) { 20 | // convert the bufferedLoaded to a percent using the video duration as a 100% 21 | double percent = 0; 22 | if (_.buffered.value.isNotEmpty) { 23 | final loaded = _.buffered.value.last.end; 24 | percent = loaded.inSeconds / _.duration.value.inSeconds; 25 | } 26 | // draw the bufferedLoaded as a container 27 | return AnimatedContainer( 28 | duration: Duration(milliseconds: 300), 29 | color: Colors.white30, 30 | width: constraints.maxWidth * percent, 31 | height: 3, 32 | ); 33 | }, 34 | ); 35 | }), 36 | ), 37 | RxBuilder( 38 | observables: [_.sliderPosition, _.duration], 39 | builder: (__) { 40 | final int value = _.sliderPosition.value.inSeconds; 41 | final double max = _.duration.value.inSeconds.toDouble(); 42 | if (value > max || max <= 0) { 43 | return Container(); 44 | } 45 | return Container( 46 | constraints: BoxConstraints( 47 | maxHeight: 30, 48 | ), 49 | padding: EdgeInsets.only(bottom: 8), 50 | alignment: Alignment.center, 51 | child: SliderTheme( 52 | data: SliderThemeData( 53 | trackShape: MSliderTrackShape(), 54 | thumbColor: _.colorTheme, 55 | activeTrackColor: _.colorTheme, 56 | trackHeight: 10, 57 | thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0), 58 | ), 59 | child: Slider( 60 | min: 0, 61 | divisions: _.duration.value.inSeconds, 62 | value: value.toDouble(), 63 | onChangeStart: (v) { 64 | _.onChangedSliderStart(); 65 | }, 66 | onChangeEnd: (v) { 67 | _.onChangedSliderEnd(); 68 | _.seekTo( 69 | Duration(seconds: v.floor()), 70 | ); 71 | }, 72 | label: printDuration(_.sliderPosition.value), 73 | max: max, 74 | onChanged: _.onChangedSlider, 75 | ), 76 | ), 77 | ); 78 | }, 79 | ) 80 | ], 81 | ); 82 | } 83 | } 84 | 85 | class MSliderTrackShape extends RoundedRectSliderTrackShape { 86 | @override 87 | Rect getPreferredRect({ 88 | required RenderBox parentBox, 89 | Offset offset = Offset.zero, 90 | SliderThemeData? sliderTheme, 91 | bool isEnabled = false, 92 | bool isDiscrete = false, 93 | }) { 94 | final double trackHeight = 1; 95 | final double trackLeft = offset.dx; 96 | final double trackTop = 97 | offset.dy + (parentBox.size.height - trackHeight) / 2 + 4; 98 | final double trackWidth = parentBox.size.width; 99 | return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/styles/controls_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/src/controller.dart'; 4 | 5 | class ControlsContainer extends StatelessWidget { 6 | final Widget child; 7 | const ControlsContainer({Key? key, required this.child}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final _ = MeeduPlayerController.of(context); 12 | return Positioned.fill( 13 | child: RxBuilder( 14 | observables: [_.showControls], 15 | builder: (__) => GestureDetector( 16 | onTap: () => _.controls = !_.showControls.value, 17 | child: AnimatedOpacity( 18 | opacity: _.showControls.value ? 1 : 0, 19 | duration: Duration(milliseconds: 300), 20 | child: AnimatedContainer( 21 | duration: Duration(milliseconds: 300), 22 | color: _.showControls.value ? Colors.black38 : Colors.transparent, 23 | child: AbsorbPointer( 24 | absorbing: !_.showControls.value, 25 | child: this.child, 26 | ), 27 | ), 28 | ), 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/styles/primary/bottom_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/helpers/responsive.dart'; 5 | import 'package:meedu_player/src/helpers/utils.dart'; 6 | import 'package:meedu_player/src/widgets/fullscreen_button.dart'; 7 | import 'package:meedu_player/src/widgets/mute_sound_button.dart'; 8 | import 'package:meedu_player/src/widgets/pip_button.dart'; 9 | import 'package:meedu_player/src/widgets/player_slider.dart'; 10 | import 'package:meedu_player/src/widgets/video_fit_button.dart'; 11 | 12 | class PrimaryBottomControls extends StatelessWidget { 13 | final Responsive responsive; 14 | const PrimaryBottomControls({Key? key, required this.responsive}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final _ = MeeduPlayerController.of(context); 20 | final fontSize = responsive.ip(2.5); 21 | final textStyle = TextStyle( 22 | color: Colors.white, 23 | fontSize: fontSize > 17 ? 17 : fontSize, 24 | ); 25 | return Positioned( 26 | left: 5, 27 | right: 0, 28 | bottom: 0, 29 | child: Row( 30 | crossAxisAlignment: CrossAxisAlignment.center, 31 | children: [ 32 | // START VIDEO POSITION 33 | RxBuilder( 34 | observables: [_.duration, _.position], 35 | builder: (__) => Text( 36 | _.duration.value.inMinutes >= 60 37 | ? printDurationWithHours(_.position.value) 38 | : printDuration(_.position.value), 39 | style: textStyle, 40 | ), 41 | ), 42 | // END VIDEO POSITION 43 | SizedBox(width: 10), 44 | Expanded( 45 | child: PlayerSlider(), 46 | ), 47 | SizedBox(width: 10), 48 | // START VIDEO DURATION 49 | RxBuilder( 50 | observables: [_.duration], 51 | builder: (__) => Text( 52 | _.duration.value.inMinutes >= 60 53 | ? printDurationWithHours(_.duration.value) 54 | : printDuration(_.duration.value), 55 | style: textStyle, 56 | ), 57 | ), 58 | // END VIDEO DURATION 59 | SizedBox(width: 15), 60 | if (_.bottomRight != null) ...[_.bottomRight!, SizedBox(width: 5)], 61 | 62 | if (_.enabledButtons.pip) PipButton(responsive: responsive), 63 | 64 | if (_.enabledButtons.videoFit) VideoFitButton(responsive: responsive), 65 | if (_.enabledButtons.muteAndSound) 66 | MuteSoundButton(responsive: responsive), 67 | 68 | if (_.enabledButtons.fullscreen) 69 | FullscreenButton( 70 | size: responsive.ip(_.fullscreen.value ? 5 : 7), 71 | ) 72 | ], 73 | ), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/styles/primary/primary_player_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:meedu_player/meedu_player.dart'; 3 | 4 | import 'package:meedu_player/src/helpers/responsive.dart'; 5 | import 'package:meedu_player/src/widgets/play_pause_button.dart'; 6 | import 'package:meedu_player/src/widgets/styles/controls_container.dart'; 7 | import 'package:meedu_player/src/widgets/styles/primary/bottom_controls.dart'; 8 | import '../../player_button.dart'; 9 | 10 | class PrimaryVideoPlayerControls extends StatelessWidget { 11 | final Responsive responsive; 12 | const PrimaryVideoPlayerControls({Key? key, required this.responsive}) 13 | : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final _ = MeeduPlayerController.of(context); 18 | 19 | return ControlsContainer( 20 | child: Stack( 21 | alignment: Alignment.center, 22 | children: [ 23 | // RENDER A CUSTOM HEADER 24 | if (_.header != null) 25 | Positioned( 26 | child: _.header!, 27 | left: 0, 28 | right: 0, 29 | top: 0, 30 | ), 31 | 32 | Row( 33 | mainAxisSize: MainAxisSize.min, 34 | children: [ 35 | if (_.enabledButtons.rewindAndfastForward) ...[ 36 | PlayerButton( 37 | onPressed: _.rewind, 38 | size: responsive.ip(_.fullscreen.value ? 8 : 12), 39 | iconColor: Colors.white, 40 | backgrounColor: Colors.transparent, 41 | iconPath: 'assets/icons/rewind.png', 42 | customIcon: _.customIcons.rewind, 43 | ), 44 | SizedBox(width: 10), 45 | ], 46 | if (_.enabledButtons.playPauseAndRepeat) 47 | PlayPauseButton( 48 | size: responsive.ip(_.fullscreen.value ? 10 : 15), 49 | ), 50 | if (_.enabledButtons.rewindAndfastForward) ...[ 51 | SizedBox(width: 10), 52 | PlayerButton( 53 | onPressed: _.fastForward, 54 | iconColor: Colors.white, 55 | backgrounColor: Colors.transparent, 56 | size: responsive.ip(_.fullscreen.value ? 8 : 12), 57 | iconPath: 'assets/icons/fast-forward.png', 58 | customIcon: _.customIcons.fastForward, 59 | ), 60 | ] 61 | ], 62 | ), 63 | 64 | PrimaryBottomControls( 65 | responsive: responsive, 66 | ), 67 | ], 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/styles/secondary/secondary_bottom_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/helpers/responsive.dart'; 5 | import 'package:meedu_player/src/helpers/utils.dart'; 6 | import 'package:meedu_player/src/widgets/fullscreen_button.dart'; 7 | import 'package:meedu_player/src/widgets/mute_sound_button.dart'; 8 | import 'package:meedu_player/src/widgets/pip_button.dart'; 9 | import 'package:meedu_player/src/widgets/play_pause_button.dart'; 10 | import 'package:meedu_player/src/widgets/player_slider.dart'; 11 | 12 | import '../../video_fit_button.dart'; 13 | 14 | class SecondaryBottomControls extends StatelessWidget { 15 | final Responsive responsive; 16 | const SecondaryBottomControls({Key? key, required this.responsive}) 17 | : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | final _ = MeeduPlayerController.of(context); 22 | final fontSize = responsive.ip(2); 23 | final textStyle = TextStyle( 24 | color: Colors.white, 25 | fontSize: fontSize > 17 ? 17 : fontSize, 26 | ); 27 | 28 | final tmp = responsive.ip(8); 29 | final buttonsSize = tmp < 45.0 ? tmp : 45.0; 30 | 31 | return Positioned( 32 | left: 0, 33 | right: 0, 34 | bottom: 0, 35 | child: Column( 36 | mainAxisSize: MainAxisSize.min, 37 | crossAxisAlignment: CrossAxisAlignment.end, 38 | children: [ 39 | Transform.translate( 40 | offset: Offset(0, 4), 41 | child: PlayerSlider(), 42 | ), 43 | Row( 44 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 45 | children: [ 46 | Row( 47 | children: [ 48 | SizedBox(width: 5), 49 | PlayPauseButton( 50 | size: buttonsSize, 51 | ), 52 | SizedBox(width: 5), 53 | RxBuilder( 54 | observables: [_.duration, _.position], 55 | builder: (__) { 56 | String text = ""; 57 | if (_.duration.value.inMinutes >= 60) { 58 | // if the duration is >= 1 hour 59 | text = 60 | "${printDurationWithHours(_.position.value)} / ${printDurationWithHours(_.duration.value)}"; 61 | } else { 62 | text = 63 | "${printDuration(_.position.value)} / ${printDuration(_.duration.value)}"; 64 | } 65 | return Padding( 66 | padding: EdgeInsets.only(right: 5), 67 | child: Text( 68 | text, 69 | style: textStyle, 70 | ), 71 | ); 72 | }, 73 | ), 74 | // PlayerButton( 75 | // onPressed: _.rewind, 76 | // size: buttonsSize, 77 | // iconColor: Colors.white, 78 | // backgrounColor: Colors.transparent, 79 | // iconPath: 'assets/icons/rewind.png', 80 | // ), 81 | // PlayerButton( 82 | // onPressed: _.fastForward, 83 | // iconColor: Colors.white, 84 | // backgrounColor: Colors.transparent, 85 | // size: buttonsSize, 86 | // iconPath: 'assets/icons/fast-forward.png', 87 | // ), 88 | SizedBox(width: 5), 89 | ], 90 | ), 91 | Row( 92 | children: [ 93 | if (_.bottomRight != null) ...[ 94 | _.bottomRight!, 95 | SizedBox(width: 10) 96 | ], 97 | if (_.enabledButtons.pip) PipButton(responsive: responsive), 98 | if (_.enabledButtons.videoFit) 99 | VideoFitButton(responsive: responsive), 100 | if (_.enabledButtons.muteAndSound) 101 | MuteSoundButton(responsive: responsive), 102 | if (_.enabledButtons.fullscreen) ...[ 103 | FullscreenButton( 104 | size: buttonsSize, 105 | ), 106 | SizedBox(width: 5), 107 | ], 108 | ], 109 | ) 110 | ], 111 | ), 112 | ], 113 | ), 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/styles/secondary/secondary_player_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:meedu_player/src/controller.dart'; 3 | import 'package:meedu_player/src/helpers/responsive.dart'; 4 | 5 | import 'package:meedu_player/src/widgets/styles/controls_container.dart'; 6 | import 'package:meedu_player/src/widgets/styles/secondary/secondary_bottom_controls.dart'; 7 | 8 | class SecondaryVideoPlayerControls extends StatelessWidget { 9 | final Responsive responsive; 10 | const SecondaryVideoPlayerControls({Key? key, required this.responsive}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final _ = MeeduPlayerController.of(context); 16 | return ControlsContainer( 17 | child: Stack( 18 | children: [ 19 | // RENDER A CUSTOM HEADER 20 | if (_.header != null) 21 | Positioned( 22 | child: _.header!, 23 | left: 0, 24 | right: 0, 25 | top: 0, 26 | ), 27 | SecondaryBottomControls( 28 | responsive: responsive, 29 | ), 30 | ], 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/lib/src/widgets/video_fit_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_meedu/rx.dart'; 3 | import 'package:meedu_player/meedu_player.dart'; 4 | import 'package:meedu_player/src/helpers/responsive.dart'; 5 | 6 | import 'player_button.dart'; 7 | 8 | class VideoFitButton extends StatelessWidget { 9 | final Responsive responsive; 10 | const VideoFitButton({Key? key, required this.responsive}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final _ = MeeduPlayerController.of(context); 15 | return RxBuilder( 16 | observables: [_.fullscreen], 17 | builder: (__) { 18 | String iconPath = 'assets/icons/fit.png'; 19 | Widget? customIcon = _.customIcons.videoFit; 20 | 21 | return PlayerButton( 22 | size: responsive.ip(_.fullscreen.value ? 5 : 7), 23 | circle: false, 24 | backgrounColor: Colors.transparent, 25 | iconColor: Colors.white, 26 | iconPath: iconPath, 27 | customIcon: customIcon, 28 | onPressed: () { 29 | _.toggleVideoFit(); 30 | }, 31 | ); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/player.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /plugin/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_meedu: 59 | dependency: "direct main" 60 | description: 61 | name: flutter_meedu 62 | url: "https://pub.dartlang.org" 63 | source: hosted 64 | version: "2.0.1" 65 | flutter_spinkit: 66 | dependency: "direct main" 67 | description: 68 | name: flutter_spinkit 69 | url: "https://pub.dartlang.org" 70 | source: hosted 71 | version: "5.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | flutter_web_plugins: 78 | dependency: transitive 79 | description: flutter 80 | source: sdk 81 | version: "0.0.0" 82 | js: 83 | dependency: transitive 84 | description: 85 | name: js 86 | url: "https://pub.dartlang.org" 87 | source: hosted 88 | version: "0.6.3" 89 | matcher: 90 | dependency: transitive 91 | description: 92 | name: matcher 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "0.12.10" 96 | meedu: 97 | dependency: "direct main" 98 | description: 99 | name: meedu 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "2.0.1" 103 | meta: 104 | dependency: "direct main" 105 | description: 106 | name: meta 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.3.0" 110 | nested: 111 | dependency: transitive 112 | description: 113 | name: nested 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.0.0" 117 | path: 118 | dependency: transitive 119 | description: 120 | name: path 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.8.0" 124 | provider: 125 | dependency: transitive 126 | description: 127 | name: provider 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "5.0.0" 131 | sky_engine: 132 | dependency: transitive 133 | description: flutter 134 | source: sdk 135 | version: "0.0.99" 136 | source_span: 137 | dependency: transitive 138 | description: 139 | name: source_span 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "1.8.0" 143 | stack_trace: 144 | dependency: transitive 145 | description: 146 | name: stack_trace 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "1.10.0" 150 | stream_channel: 151 | dependency: transitive 152 | description: 153 | name: stream_channel 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "2.1.0" 157 | string_scanner: 158 | dependency: transitive 159 | description: 160 | name: string_scanner 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "1.1.0" 164 | term_glyph: 165 | dependency: transitive 166 | description: 167 | name: term_glyph 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "1.2.0" 171 | test_api: 172 | dependency: transitive 173 | description: 174 | name: test_api 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "0.2.19" 178 | typed_data: 179 | dependency: transitive 180 | description: 181 | name: typed_data 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "1.3.0" 185 | vector_math: 186 | dependency: transitive 187 | description: 188 | name: vector_math 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "2.1.0" 192 | video_player: 193 | dependency: "direct main" 194 | description: 195 | name: video_player 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "2.0.0" 199 | video_player_platform_interface: 200 | dependency: transitive 201 | description: 202 | name: video_player_platform_interface 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "4.0.0" 206 | video_player_web: 207 | dependency: transitive 208 | description: 209 | name: video_player_web 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "2.0.0" 213 | sdks: 214 | dart: ">=2.12.0 <3.0.0" 215 | flutter: ">=2.0.0" 216 | -------------------------------------------------------------------------------- /plugin/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: meedu_player 2 | description: A new Flutter UI video controls for the flutter video_player plugin. 3 | version: 0.5.0-nullsafety.0 4 | homepage: https://github.com/the-meedu-app/flutter-meedu-player/tree/master/plugin 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=2.0.0" 9 | 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | video_player: ^2.0.0 15 | meedu: ^2.0.1 16 | flutter_meedu: ^2.0.1 17 | flutter_spinkit: ^5.0.0 18 | meta: ^1.3.0 19 | 20 | 21 | dev_dependencies: 22 | flutter_test: 23 | sdk: flutter 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://dart.dev/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | # This section identifies this Flutter project as a plugin project. 31 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily 32 | # be modified. They are used by the tooling to maintain consistency when 33 | # adding or updating assets for this project. 34 | plugin: 35 | platforms: 36 | android: 37 | package: app.meedu.player 38 | pluginClass: MeeduPlayerPlugin 39 | ios: 40 | pluginClass: MeeduPlayerPlugin 41 | 42 | assets: 43 | - assets/icons/fast-forward.png 44 | - assets/icons/fullscreen.png 45 | - assets/icons/minimize.png 46 | - assets/icons/mute.png 47 | - assets/icons/play.png 48 | - assets/icons/pause.png 49 | - assets/icons/repeat.png 50 | - assets/icons/rewind.png 51 | - assets/icons/sound.png 52 | - assets/icons/picture-in-picture.png 53 | - assets/icons/fit.png 54 | # 55 | # For details regarding assets in packages, see 56 | # https://flutter.dev/assets-and-images/#from-packages 57 | # 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.dev/assets-and-images/#resolution-aware. 60 | 61 | # To add custom fonts to your plugin package, add a fonts section here, 62 | # in this "flutter" section. Each entry in this list should have a 63 | # "family" key with the font family name, and a "fonts" key with a 64 | # list giving the asset and other descriptors for the font. For 65 | # example: 66 | # fonts: 67 | # - family: Schyler 68 | # fonts: 69 | # - asset: fonts/Schyler-Regular.ttf 70 | # - asset: fonts/Schyler-Italic.ttf 71 | # style: italic 72 | # - family: Trajan Pro 73 | # fonts: 74 | # - asset: fonts/TrajanPro.ttf 75 | # - asset: fonts/TrajanPro_Bold.ttf 76 | # weight: 700 77 | # 78 | # For details regarding fonts in packages, see 79 | # https://flutter.dev/custom-fonts/#from-packages --------------------------------------------------------------------------------