├── .github
└── workflows
│ ├── build_android.yml
│ ├── build_ios.yml
│ ├── build_web.yml
│ ├── create-documentation-pr.yml
│ ├── create-release-from-changelog.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ └── src
│ │ └── main
│ │ └── java
│ │ └── io
│ │ └── flutter
│ │ └── plugins
│ │ └── GeneratedPluginRegistrant.java
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── video
│ └── api
│ └── flutter
│ └── player
│ ├── ApiVideoPlayerPlugin.kt
│ ├── Extensions.kt
│ ├── FlutterPlayerController.kt
│ ├── FlutterPlayerInterface.kt
│ ├── FlutterPlayerView.kt
│ └── MethodCallHandler.kt
├── example
├── .gitignore
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── video
│ │ │ │ │ └── api
│ │ │ │ │ └── apivideo_player_example
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ ├── ic_api_video.xml
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.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
│ │ │ ├── 1024.png
│ │ │ ├── 120 1.png
│ │ │ ├── 120 2.png
│ │ │ ├── 152.png
│ │ │ ├── 167.png
│ │ │ ├── 180.png
│ │ │ ├── 20.png
│ │ │ ├── 29.png
│ │ │ ├── 40 1.png
│ │ │ ├── 40 2.png
│ │ │ ├── 40.png
│ │ │ ├── 58 1.png
│ │ │ ├── 58.png
│ │ │ ├── 60.png
│ │ │ ├── 76.png
│ │ │ ├── 80 1.png
│ │ │ ├── 80.png
│ │ │ ├── 87.png
│ │ │ └── Contents.json
│ │ └── 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
├── pubspec.yaml
├── test
│ └── widget_test.dart
└── web
│ ├── favicon.png
│ ├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
│ ├── index.html
│ └── manifest.json
├── ios
├── .gitignore
├── Assets
│ └── .gitkeep
├── Classes
│ ├── ApiVideoPlayerPlugin.h
│ ├── ApiVideoPlayerPlugin.m
│ ├── FlutterPlayerController.swift
│ ├── FlutterPlayerView.swift
│ ├── MethodCallHandler.swift
│ └── SwiftApiVideoPlayerPlugin.swift
└── apivideo_player.podspec
├── lib
├── apivideo_player.dart
└── src
│ ├── apivideo_player_controller.dart
│ ├── apivideo_player_life_cycle_observer.dart
│ ├── apivideo_types.dart
│ ├── apivideo_types.g.dart
│ ├── platform
│ ├── apivideo_mobile_player_platform.dart
│ ├── apivideo_player_platform_interface.dart
│ ├── apivideo_player_web.dart
│ └── web
│ │ ├── javascript_controller.dart
│ │ └── utils
│ │ ├── conversion.dart
│ │ └── player_event_type_extension.dart
│ ├── style
│ ├── apivideo_colors.dart
│ ├── apivideo_icons.dart
│ ├── apivideo_label.dart
│ ├── apivideo_style.dart
│ └── fonts
│ │ └── ApiVideoIcons.ttf
│ ├── utils
│ └── extensions
│ │ └── duration_extension.dart
│ └── widgets
│ ├── apivideo_player.dart
│ ├── apivideo_player_controls_bar.dart
│ ├── apivideo_player_opacity.dart
│ ├── apivideo_player_overlay.dart
│ ├── apivideo_player_settings_bar.dart
│ ├── apivideo_player_time_slider.dart
│ ├── apivideo_player_video.dart
│ ├── apivideo_player_volume_slider.dart
│ └── common
│ └── apivideo_player_multi_text_button.dart
├── pubspec.yaml
└── test
├── apivideo_player_test.dart
└── apivideo_types.dart
/.github/workflows/build_android.yml:
--------------------------------------------------------------------------------
1 | name: Build Android
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.dart'
7 | - '**.yaml'
8 | - 'android/**'
9 |
10 | jobs:
11 | build_android:
12 | name: Build Android
13 | uses: apivideo/.github/.github/workflows/flutter_build_android.yml@main
14 | with:
15 | cache: true
16 |
--------------------------------------------------------------------------------
/.github/workflows/build_ios.yml:
--------------------------------------------------------------------------------
1 | name: Build iOS
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.dart'
7 | - '**.yaml'
8 | - 'ios/**'
9 |
10 | jobs:
11 | build_ios:
12 | name: Build iOS
13 | uses: apivideo/.github/.github/workflows/flutter_build_ios.yml@main
14 | with:
15 | cache: true
16 |
--------------------------------------------------------------------------------
/.github/workflows/build_web.yml:
--------------------------------------------------------------------------------
1 | name: Build web
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.dart'
7 | - '**.yaml'
8 |
9 | jobs:
10 | build_web:
11 | name: Build web
12 | uses: apivideo/.github/.github/workflows/flutter_build_web.yml@main
13 | with:
14 | cache: true
15 |
--------------------------------------------------------------------------------
/.github/workflows/create-documentation-pr.yml:
--------------------------------------------------------------------------------
1 | name: Create documentation PR
2 | on:
3 | # Trigger the workflow on pull requests targeting the main branch
4 | pull_request:
5 | types: [assigned, unassigned, opened, reopened, synchronize, edited, labeled, unlabeled, edited, closed]
6 | branches:
7 | - main
8 |
9 | jobs:
10 | create_documentation_pr:
11 | if: github.event.action != 'closed'
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Check out current repository code
17 | uses: actions/checkout@v2
18 |
19 | - name: Create the documentation pull request
20 | uses: apivideo/api.video-create-readme-file-pull-request-action@main
21 | with:
22 | source-file-path: "README.md"
23 | destination-repository: apivideo/api.video-documentation
24 | destination-path: sdks/player
25 | destination-filename: apivideo-flutter-player.md
26 | pat: "${{ secrets.PAT }}"
27 |
--------------------------------------------------------------------------------
/.github/workflows/create-release-from-changelog.yml:
--------------------------------------------------------------------------------
1 | name: Create draft release from CHANGELOG.md
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'CHANGELOG.md'
7 |
8 | jobs:
9 | update-documentation:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Create draft release if needed
14 | uses: apivideo/api.video-release-from-changelog-action@main
15 | with:
16 | github-auth-token: ${{ secrets.GITHUB_TOKEN }}
17 | prefix: v
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish to pub.dev
2 | on:
3 | release:
4 | types: [ published ]
5 | jobs:
6 | build_ios:
7 | name: Build iOS
8 | uses: apivideo/.github/.github/workflows/flutter_build_ios.yml@main
9 | build_android:
10 | name: Build Android
11 | uses: apivideo/.github/.github/workflows/flutter_build_android.yml@main
12 | build_web:
13 | name: Build web
14 | uses: apivideo/.github/.github/workflows/flutter_build_web.yml@main
15 | publish:
16 | name: Publish to pub.dev
17 | needs: [ build_ios, build_android, build_web ]
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v3
21 | - name: Publish
22 | uses: sakebook/actions-flutter-pub-publisher@v1.4.1
23 | with:
24 | credential: ${{ secrets.CREDENTIAL_JSON }}
25 | flutter_package: true
26 | skip_test: false
27 | dry_run: false
28 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests and analysis
2 |
3 | on: [ push ]
4 |
5 | jobs:
6 | run_tests:
7 | name: Run tests
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - name: Setup Flutter
12 | id: flutter-action
13 | uses: subosito/flutter-action@v2
14 | with:
15 | channel: 'stable'
16 | cache: ${{ inputs.cache }}
17 | cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.yaml') }}
18 | - name: Install dependencies
19 | run: flutter pub get
20 | - name: Run tests
21 | run: flutter test
22 |
23 | analyze_code:
24 | name: Analyze code
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v3
28 | - name: Setup Flutter
29 | id: flutter-action
30 | uses: subosito/flutter-action@v2
31 | with:
32 | channel: 'stable'
33 | cache: ${{ inputs.cache }}
34 | cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.yaml') }}
35 | - name: Install dependencies
36 | run: flutter pub get
37 | - name: Flutter analyze
38 | run: flutter analyze
39 |
40 | calculate_score:
41 | name: Calculate score
42 | runs-on: ubuntu-latest
43 | steps:
44 | - uses: actions/checkout@v3
45 | - name: Setup Flutter
46 | id: flutter-action
47 | uses: subosito/flutter-action@v2
48 | with:
49 | channel: 'stable'
50 | cache: ${{ inputs.cache }}
51 | cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.yaml') }}
52 | - name: Install dependencies
53 | run: flutter pub get
54 | - name: Install pana
55 | run: flutter pub global activate pana
56 | - name: Calculate pub points
57 | run: flutter pub pub global run pana --exit-code-threshold 0 .
58 |
59 | test_publishing:
60 | name: Test publishing
61 | runs-on: ubuntu-latest
62 | steps:
63 | - uses: actions/checkout@v3
64 | - name: Setup Flutter
65 | id: flutter-action
66 | uses: subosito/flutter-action@v2
67 | with:
68 | channel: 'stable'
69 | cache: ${{ inputs.cache }}
70 | cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.yaml') }}
71 | - name: Install dependencies
72 | run: flutter pub get
73 | - name: Flutter publish dry run
74 | run: flutter pub publish --dry-run
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | .packages
30 | build/
31 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: eb6d86ee27deecba4a83536aa20f366a6044895c
8 | channel: stable
9 |
10 | project_type: plugin
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
17 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
18 | - platform: android
19 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
20 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
21 | - platform: ios
22 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
23 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
24 | - platform: web
25 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
26 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
27 |
28 | # User provided section
29 |
30 | # List of Local paths (relative to this file) that should be
31 | # ignored by the migrate tool.
32 | #
33 | # Files that are not part of the templates will be ignored by default.
34 | unmanaged_files:
35 | - 'lib/main.dart'
36 | - 'ios/Runner.xcodeproj/project.pbxproj'
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All changes to this project will be documented in this file.
4 |
5 | ## [1.4.0] - 2024-07-26
6 |
7 | - Use Analytics endpoint v2
8 | - android: gradle: remove imperative apply
9 | - android: fix `setIsMuted` method. See [#56](https://github.com/apivideo/api.video-flutter-player/issues/56)
10 | - android: fix crash due to missing `release` of the `MediaSession`. See [#58](https://github.com/apivideo/api.video-flutter-player/issues/58)
11 | - example: infer video type from mediaId
12 | - upgrade dependencies
13 |
14 | ## [1.3.0] - 2024-03-01
15 |
16 | - iOS: add support for private live stream
17 | - web: format file to fix pub points
18 |
19 | ## [1.2.2] - 2024-02-15
20 |
21 | - Android: upgrade to gradle 8, AGP and Kotlin to 1.9
22 | - Fix few warnings
23 |
24 | ## [1.2.1] - 2023-12-05
25 |
26 | - Add an API to set the duration when the overlay is displayed
27 | - Web: Inject the player sdk bundle to simplify web integration
28 | - Web: Fix a crash when the player is created
29 | - Fix a crash when the player is disposed due to double dispose
30 | - Improve comments
31 | - Privatize some methods
32 |
33 | ## [1.2.0] - 2023-10-11
34 |
35 | - Add support for live stream videos
36 | - Add support for Android >= 21
37 | - Add support for Android 34
38 | - Add a `fit` parameter to `ApiVideoPlayer` to set how the video is displayed in its box
39 | - Improve the customization of `ApiVideoPlayer` with `PlayerStyle`
40 | - Refactor widgets to split into several widgets
41 |
42 | ## [1.1.0] - 2023-07-26
43 |
44 | - Add support for private videos
45 | - Add support for playback speed
46 | - iOS: add support from iOS 11
47 | - Web: close player events when player is disposed
48 | - Web: remove CSS border on player
49 | - Web: fix player sdk event on release and profile mode
50 | - Web: fix `getVideoSize` API that caused a bad aspect ratio with border
51 | - Android: fix the duration of the video when the video is not loaded
52 | - Android: fix crash when the current time < 0
53 | - Android: fix a crash due to obfuscation (
54 | see [#43](https://github.com/apivideo/api.video-flutter-player/issues/43))
55 |
56 | ## [1.0.0] - 2022-10-10
57 |
58 | - First version
59 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 api.video
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.
22 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | # Additional information about this file can be found at
4 | # https://dart.dev/guides/language/analysis-options
5 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .cxx
10 |
--------------------------------------------------------------------------------
/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java:
--------------------------------------------------------------------------------
1 | package io.flutter.plugins;
2 |
3 | import androidx.annotation.Keep;
4 | import androidx.annotation.NonNull;
5 | import io.flutter.Log;
6 |
7 | import io.flutter.embedding.engine.FlutterEngine;
8 |
9 | /**
10 | * Generated file. Do not edit.
11 | * This file is generated by the Flutter tool based on the
12 | * plugins that support the Android platform.
13 | */
14 | @Keep
15 | public final class GeneratedPluginRegistrant {
16 | private static final String TAG = "GeneratedPluginRegistrant";
17 | public static void registerWith(@NonNull FlutterEngine flutterEngine) {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'video.api.flutter.player'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | ext.kotlin_version = '1.9.22'
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:8.2.2'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | rootProject.allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | apply plugin: 'com.android.library'
25 | apply plugin: 'kotlin-android'
26 |
27 | android {
28 | compileSdk 34
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 |
43 | defaultConfig {
44 | minSdkVersion 21
45 | }
46 |
47 | namespace = "video.api.flutter.player"
48 | }
49 |
50 | dependencies {
51 | implementation 'video.api:android-player:1.6.0'
52 | }
53 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/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-8.2-all.zip
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'apivideo_player'
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/video/api/flutter/player/ApiVideoPlayerPlugin.kt:
--------------------------------------------------------------------------------
1 | package video.api.flutter.player
2 |
3 | import android.content.Context
4 | import io.flutter.embedding.engine.plugins.FlutterPlugin
5 | import io.flutter.plugin.common.BinaryMessenger
6 | import io.flutter.view.TextureRegistry
7 |
8 | /** ApiVideoPlayerPlugin */
9 | class ApiVideoPlayerPlugin : FlutterPlugin {
10 | private lateinit var textureRegistry: TextureRegistry
11 | private lateinit var messenger: BinaryMessenger
12 | private lateinit var applicationContext: Context
13 | private lateinit var api: MethodCallHandler
14 |
15 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
16 | textureRegistry = flutterPluginBinding.textureRegistry
17 | messenger = flutterPluginBinding.binaryMessenger
18 | applicationContext = flutterPluginBinding.applicationContext
19 | api = MethodCallHandler(
20 | messenger,
21 | FlutterPlayerController(textureRegistry, messenger, applicationContext)
22 | )
23 | }
24 |
25 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
26 | api.dispose()
27 | }
28 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/video/api/flutter/player/Extensions.kt:
--------------------------------------------------------------------------------
1 | package video.api.flutter.player
2 |
3 | import video.api.player.models.VideoOptions
4 | import video.api.player.models.VideoType
5 | import java.security.InvalidParameterException
6 |
7 | val Map.videoOptions: VideoOptions
8 | get() = VideoOptions(
9 | this["videoId"] as String,
10 | (this["type"] as String).toVideoType(),
11 | this["token"] as String?
12 | )
13 |
14 | fun String.toVideoType(): VideoType {
15 | return if (this == "vod") {
16 | VideoType.VOD
17 | } else if (this == "live") {
18 | VideoType.LIVE
19 | } else {
20 | throw InvalidParameterException("$this is an unknown video type")
21 | }
22 | }
23 |
24 | fun VideoType.toFlutterString(): String {
25 | return when (this) {
26 | VideoType.VOD -> "vod"
27 | VideoType.LIVE -> "live"
28 | else -> throw InvalidParameterException("$this is an unknown video type")
29 | }
30 | }
31 |
32 | fun Int.msToFloat(): Float {
33 | return this.toFloat() / 1000
34 | }
35 |
36 | fun Float.toMs(): Int {
37 | return (this * 1000).toInt()
38 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/video/api/flutter/player/FlutterPlayerController.kt:
--------------------------------------------------------------------------------
1 | package video.api.flutter.player
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.util.Size
6 | import io.flutter.plugin.common.BinaryMessenger
7 | import io.flutter.view.TextureRegistry
8 | import video.api.player.models.VideoOptions
9 |
10 |
11 | class FlutterPlayerController(
12 | private val textureRegistry: TextureRegistry,
13 | private val messenger: BinaryMessenger,
14 | private val applicationContext: Context
15 | ) : FlutterPlayerInterface {
16 | private val players = mutableMapOf()
17 |
18 | override fun isCreated(textureId: Long): Boolean {
19 | return players.containsKey(textureId)
20 | }
21 |
22 | override fun initialize(autoplay: Boolean): Long {
23 | val player = FlutterPlayerView(
24 | applicationContext,
25 | messenger,
26 | textureRegistry,
27 | null,
28 | autoplay
29 | )
30 | players[player.textureId] = player
31 |
32 | return player.textureId
33 | }
34 |
35 | override fun dispose(textureId: Long) {
36 | players[textureId]?.release() ?: Log.e(TAG, "Unknown player $textureId")
37 | players.remove(textureId)
38 | }
39 |
40 | override fun disposeAll() {
41 | players.values.forEach { it.release() }
42 | players.clear()
43 | }
44 |
45 | override fun isPlaying(textureId: Long): Boolean {
46 | return players[textureId]?.isPlaying ?: run {
47 | Log.e(TAG, "Unknown player $textureId")
48 | false
49 | }
50 | }
51 |
52 | override fun isLive(textureId: Long): Boolean {
53 | return players[textureId]?.isLive ?: run {
54 | Log.e(TAG, "Unknown player $textureId")
55 | false
56 | }
57 | }
58 |
59 | override fun setCurrentTime(textureId: Long, currentTime: Int) {
60 | players[textureId]?.let { it.currentTime = currentTime.msToFloat() } ?: Log.e(
61 | TAG,
62 | "Unknown player $textureId"
63 | )
64 | }
65 |
66 | override fun getCurrentTime(textureId: Long): Int {
67 | return players[textureId]?.currentTime?.toMs() ?: run {
68 | Log.e(TAG, "Unknown player $textureId")
69 | 0
70 | }
71 | }
72 |
73 | override fun getDuration(textureId: Long): Int {
74 | return players[textureId]?.duration?.toMs() ?: run {
75 | Log.e(TAG, "Unknown player $textureId")
76 | 0
77 | }
78 | }
79 |
80 | override fun getVideoOptions(textureId: Long): VideoOptions? {
81 | return players[textureId]?.videoOptions ?: run {
82 | Log.e(TAG, "Unknown player $textureId")
83 | null
84 | }
85 | }
86 |
87 | override fun setVideoOptions(textureId: Long, videoOptions: VideoOptions) {
88 | players[textureId]?.let { it.videoOptions = videoOptions } ?: Log.e(
89 | TAG,
90 | "Unknown player $textureId"
91 | )
92 | }
93 |
94 | override fun getAutoplay(textureId: Long): Boolean {
95 | return players[textureId]?.isAutoplay ?: run {
96 | Log.e(TAG, "Unknown player $textureId")
97 | false
98 | }
99 | }
100 |
101 | override fun setAutoplay(textureId: Long, autoplay: Boolean) {
102 | players[textureId]?.let { it.isAutoplay = autoplay } ?: Log.e(
103 | TAG,
104 | "Unknown player $textureId"
105 | )
106 | }
107 |
108 | override fun getIsMuted(textureId: Long): Boolean {
109 | return players[textureId]?.isMuted ?: run {
110 | Log.e(TAG, "Unknown player $textureId")
111 | false
112 | }
113 | }
114 |
115 | override fun setIsMuted(textureId: Long, isMuted: Boolean) {
116 | players[textureId]?.let { it.isMuted = isMuted } ?: Log.e(
117 | TAG,
118 | "Unknown player $textureId"
119 | )
120 | }
121 |
122 | override fun getIsLooping(textureId: Long): Boolean {
123 | return players[textureId]?.isLooping ?: run {
124 | Log.e(TAG, "Unknown player $textureId")
125 | false
126 | }
127 | }
128 |
129 | override fun setIsLooping(textureId: Long, isLooping: Boolean) {
130 | players[textureId]?.let { it.isLooping = isLooping } ?: Log.e(
131 | TAG,
132 | "Unknown player $textureId"
133 | )
134 | }
135 |
136 | override fun getVolume(textureId: Long): Float {
137 | return players[textureId]?.volume ?: run {
138 | Log.e(TAG, "Unknown player $textureId")
139 | 0.0F
140 | }
141 | }
142 |
143 | override fun setVolume(textureId: Long, volume: Float) {
144 | players[textureId]?.let { it.volume = volume } ?: Log.e(
145 | TAG,
146 | "Unknown player $textureId"
147 | )
148 | }
149 |
150 | override fun getVideoSize(textureId: Long): Size? {
151 | return players[textureId]?.videoSize ?: run {
152 | Log.e(TAG, "Unknown player $textureId")
153 | null
154 | }
155 | }
156 |
157 | override fun getPlaybackSpeed(textureId: Long): Double {
158 | return players[textureId]?.playbackSpeed?.toDouble() ?: run {
159 | Log.e(TAG, "Unknown player $textureId")
160 | 0.0
161 | }
162 | }
163 |
164 | override fun setPlaybackSpeed(textureId: Long, playbackSpeed: Double) {
165 | players[textureId]?.let { it.playbackSpeed = playbackSpeed.toFloat() } ?: Log.e(
166 | TAG,
167 | "Unknown player $textureId"
168 | )
169 | }
170 |
171 | override fun play(textureId: Long) {
172 | players[textureId]?.play() ?: Log.e(TAG, "Unknown player $textureId")
173 | }
174 |
175 | override fun pause(textureId: Long) {
176 | players[textureId]?.pause() ?: Log.e(TAG, "Unknown player $textureId")
177 | }
178 |
179 | override fun seek(textureId: Long, offset: Int) {
180 | players[textureId]?.seek(offset.msToFloat()) ?: Log.e(TAG, "Unknown player $textureId")
181 | }
182 |
183 | companion object {
184 | private const val TAG = "FlutterPlayerController"
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/video/api/flutter/player/FlutterPlayerInterface.kt:
--------------------------------------------------------------------------------
1 | package video.api.flutter.player
2 |
3 | import android.util.Size
4 | import video.api.player.models.VideoOptions
5 |
6 | interface FlutterPlayerInterface {
7 | fun isCreated(textureId: Long): Boolean
8 | fun initialize(autoplay: Boolean): Long
9 | fun dispose(textureId: Long)
10 | fun disposeAll()
11 |
12 | fun isPlaying(textureId: Long): Boolean
13 | fun isLive(textureId: Long): Boolean
14 | fun getCurrentTime(textureId: Long): Int
15 | fun setCurrentTime(textureId: Long, currentTime: Int)
16 | fun getDuration(textureId: Long): Int
17 | fun getVideoOptions(textureId: Long): VideoOptions?
18 | fun setVideoOptions(textureId: Long, videoOptions: VideoOptions)
19 | fun getAutoplay(textureId: Long): Boolean
20 | fun setAutoplay(textureId: Long, autoplay: Boolean)
21 | fun getIsMuted(textureId: Long): Boolean
22 | fun setIsMuted(textureId: Long, isMuted: Boolean)
23 | fun getIsLooping(textureId: Long): Boolean
24 | fun setIsLooping(textureId: Long, isLooping: Boolean)
25 | fun getVolume(textureId: Long): Float
26 | fun setVolume(textureId: Long, volume: Float)
27 | fun getVideoSize(textureId: Long): Size?
28 | fun getPlaybackSpeed(textureId: Long): Double
29 | fun setPlaybackSpeed(textureId: Long, playbackSpeed: Double)
30 |
31 | fun play(textureId: Long)
32 | fun pause(textureId: Long)
33 | fun seek(textureId: Long, offset: Int)
34 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/video/api/flutter/player/FlutterPlayerView.kt:
--------------------------------------------------------------------------------
1 | package video.api.flutter.player
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.util.Size
6 | import android.view.Surface
7 | import io.flutter.plugin.common.BinaryMessenger
8 | import io.flutter.plugin.common.EventChannel
9 | import io.flutter.plugin.common.EventChannel.EventSink
10 | import io.flutter.view.TextureRegistry
11 | import video.api.player.ApiVideoPlayerController
12 | import video.api.player.models.VideoOptions
13 | import video.api.player.models.VideoType
14 |
15 | class FlutterPlayerView(
16 | context: Context,
17 | messenger: BinaryMessenger,
18 | textureRegistry: TextureRegistry,
19 | initialVideoOptions: VideoOptions? = null,
20 | autoplay: Boolean = false
21 | ) {
22 | private val surfaceTextureEntry = textureRegistry.createSurfaceTexture()
23 | val textureId = surfaceTextureEntry.id()
24 | private val surface = Surface(surfaceTextureEntry.surfaceTexture())
25 | private val listener = object : ApiVideoPlayerController.Listener {
26 | override fun onReady() {
27 | val event = mutableMapOf()
28 | event["type"] = "ready"
29 | eventSink?.success(event)
30 | }
31 |
32 | override fun onPlay() {
33 | val event = mutableMapOf()
34 | event["type"] = "played"
35 | eventSink?.success(event)
36 | }
37 |
38 | override fun onPause() {
39 | val event = mutableMapOf()
40 | event["type"] = "paused"
41 | eventSink?.success(event)
42 | }
43 |
44 | override fun onEnd() {
45 | val event = mutableMapOf()
46 | event["type"] = "ended"
47 | eventSink?.success(event)
48 | }
49 |
50 | override fun onSeek() {
51 | val event = mutableMapOf()
52 | event["type"] = "seek"
53 | eventSink?.success(event)
54 | }
55 |
56 | override fun onError(error: Exception) {
57 | Log.e(TAG, "An error occurred: ${error.message}", error)
58 | eventSink?.error(error::class.java.name, error.message, error)
59 | }
60 | }
61 |
62 | private val playerController = initialVideoOptions?.let {
63 | ApiVideoPlayerController(
64 | context,
65 | it,
66 | listener = listener,
67 | surface = surface,
68 | initialAutoplay = autoplay
69 | )
70 | } ?: ApiVideoPlayerController(
71 | context,
72 | null,
73 | listener = listener,
74 | surface = surface,
75 | initialAutoplay = autoplay
76 | )
77 | private var eventSink: EventSink? = null
78 | private val eventChannel = EventChannel(messenger, "video.api.player/events$textureId")
79 |
80 | init {
81 | eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
82 | override fun onListen(arguments: Any?, events: EventSink?) {
83 | eventSink = events
84 | }
85 |
86 | override fun onCancel(arguments: Any?) {
87 | eventSink?.endOfStream()
88 | eventSink = null
89 | }
90 | })
91 | }
92 |
93 | var videoOptions: VideoOptions?
94 | get() = playerController.videoOptions
95 | set(value) {
96 | playerController.videoOptions = value
97 | }
98 |
99 | var isAutoplay: Boolean
100 | get() = playerController.autoplay
101 | set(value) {
102 | playerController.autoplay = value
103 | }
104 |
105 | var isMuted: Boolean
106 | get() = playerController.isMuted
107 | set(value) {
108 | playerController.isMuted = value
109 | }
110 |
111 | var isLooping: Boolean
112 | get() = playerController.isLooping
113 | set(value) {
114 | playerController.isLooping = value
115 | }
116 |
117 | var volume: Float
118 | get() = playerController.volume
119 | set(value) {
120 | playerController.volume = value
121 | }
122 |
123 | val isPlaying: Boolean
124 | get() = playerController.isPlaying
125 |
126 | val isLive: Boolean
127 | get() = playerController.isLive
128 |
129 | var currentTime: Float
130 | get() = playerController.currentTime
131 | set(value) {
132 | eventSink?.success(mapOf("type" to "seekStarted"))
133 | playerController.currentTime = value
134 | }
135 |
136 | val duration: Float
137 | get() = playerController.duration
138 |
139 | val videoSize: Size?
140 | get() = playerController.videoSize
141 |
142 | var playbackSpeed: Float
143 | get() = playerController.playbackSpeed
144 | set(value) {
145 | playerController.playbackSpeed = value
146 | }
147 |
148 | fun play() = playerController.play()
149 | fun pause() = playerController.pause()
150 |
151 | fun seek(offset: Float) {
152 | eventSink?.success(mapOf("type" to "seekStarted"))
153 | playerController.seek(offset)
154 | }
155 |
156 | fun release() {
157 | eventChannel.setStreamHandler(null)
158 | playerController.stop()
159 | surfaceTextureEntry.release()
160 | surface.release()
161 | playerController.release()
162 | }
163 |
164 | companion object {
165 | private const val TAG = "FlutterPlayerView"
166 | }
167 | }
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | .vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 | /pubspec.lock
35 |
36 | # Web related
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
44 | # Android Studio will place build artifacts here
45 | /android/app/debug
46 | /android/app/profile
47 | /android/app/release
48 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # apivideo_player_example
2 |
3 | Demonstrates how to use the apivideo_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://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/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 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | compileSdk 34
27 | ndkVersion flutter.ndkVersion
28 |
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 |
34 | kotlinOptions {
35 | jvmTarget = '1.8'
36 | }
37 |
38 | sourceSets {
39 | main.java.srcDirs += 'src/main/kotlin'
40 | }
41 |
42 | defaultConfig {
43 | applicationId "video.api.flutter.player.example"
44 | // You can update the following values to match your application needs.
45 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
46 | minSdkVersion 21
47 | targetSdkVersion flutter.targetSdkVersion
48 | versionCode flutterVersionCode.toInteger()
49 | versionName flutterVersionName
50 | }
51 |
52 | buildTypes {
53 | release {
54 | // TODO: Add your own signing config for the release build.
55 | // Signing with the debug keys for now, so `flutter run --release` works.
56 | minifyEnabled true
57 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
58 | signingConfig signingConfigs.debug
59 | }
60 | }
61 | namespace 'video.api.flutter.player.example'
62 | }
63 |
64 | flutter {
65 | source '../..'
66 | }
67 |
68 | dependencies {
69 | }
70 |
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -dontwarn org.slf4j.impl.StaticLoggerBinder
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/video/api/apivideo_player_example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package video.api.flutter.player.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/ic_api_video.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.nonTransitiveRClass=false
5 | android.nonFinalResIds=false
6 |
--------------------------------------------------------------------------------
/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-8.2-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.2.2" apply false
22 | id "org.jetbrains.kotlin.android" version "1.9.22" apply false
23 | }
24 |
25 | include ":app"
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 14.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '12.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 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - apivideo_player (0.0.1):
3 | - ApiVideoPlayer (= 1.3.0)
4 | - Flutter
5 | - ApiVideoPlayer (1.3.0):
6 | - ApiVideoPlayerAnalytics (= 2.0.0)
7 | - ApiVideoPlayerAnalytics (2.0.0)
8 | - Flutter (1.0.0)
9 | - pointer_interceptor_ios (0.0.1):
10 | - Flutter
11 |
12 | DEPENDENCIES:
13 | - apivideo_player (from `.symlinks/plugins/apivideo_player/ios`)
14 | - Flutter (from `Flutter`)
15 | - pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
16 |
17 | SPEC REPOS:
18 | trunk:
19 | - ApiVideoPlayer
20 | - ApiVideoPlayerAnalytics
21 |
22 | EXTERNAL SOURCES:
23 | apivideo_player:
24 | :path: ".symlinks/plugins/apivideo_player/ios"
25 | Flutter:
26 | :path: Flutter
27 | pointer_interceptor_ios:
28 | :path: ".symlinks/plugins/pointer_interceptor_ios/ios"
29 |
30 | SPEC CHECKSUMS:
31 | apivideo_player: ebd05d204d7f8bf03aa8a01f603d7b869a9dca60
32 | ApiVideoPlayer: f29a6b54b3eb5904c1bd123d5fc2548d1699e7dc
33 | ApiVideoPlayerAnalytics: df5fe80fb7dbb333efd9b47e8797dc24182ee412
34 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
35 | pointer_interceptor_ios: 9280618c0b2eeb80081a343924aa8ad756c21375
36 |
37 | PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048
38 |
39 | COCOAPODS: 1.15.2
40 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
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 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 1.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 2.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 1.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 2.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58 1.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80 1.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "58 1.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "87.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "80 1.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "120 2.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "120 1.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "180.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "40 1.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "58.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "40 2.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "80.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "152.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "167.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | FltPlayer
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | FltPlayer
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIMainStoryboardFile
32 | Main
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UIViewControllerBasedStatusBarAppearance
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:apivideo_player/apivideo_player.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | void main() {
5 | runApp(const MyApp());
6 | }
7 |
8 | class MyApp extends StatefulWidget {
9 | const MyApp({super.key});
10 |
11 | @override
12 | State createState() => _MyAppState();
13 | }
14 |
15 | class _MyAppState extends State {
16 | final TextEditingController _videoIdTextEditingController =
17 | TextEditingController(text: 'vi77Dgk0F8eLwaFOtC5870yn');
18 | ApiVideoPlayerController? _controller;
19 | final TextEditingController _tokenTextEditingController =
20 | TextEditingController(text: '');
21 |
22 | @override
23 | void initState() {
24 | buildVideoOptions();
25 | super.initState();
26 | }
27 |
28 | void buildVideoOptions() {
29 | final token = _tokenTextEditingController.text.isEmpty
30 | ? null
31 | : _tokenTextEditingController.text;
32 |
33 | final videoOptions = VideoOptions(
34 | videoId: _videoIdTextEditingController.text,
35 | token: token);
36 |
37 | if (_controller == null) {
38 | _controller =
39 | ApiVideoPlayerController(videoOptions: videoOptions, autoplay: true);
40 | } else {
41 | _controller?.setVideoOptions(videoOptions);
42 | }
43 | }
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | return MaterialApp(
48 | theme: ThemeData(
49 | textButtonTheme: TextButtonThemeData(
50 | style: TextButton.styleFrom(
51 | foregroundColor: const Color(0xFFFA5B30),
52 | ),
53 | ),
54 | ),
55 | home: Builder(builder: (context) {
56 | return Scaffold(
57 | body: SafeArea(
58 | child: SingleChildScrollView(
59 | child: Column(
60 | children: [
61 | Padding(
62 | padding: const EdgeInsets.all(30.0),
63 | child: TextField(
64 | decoration: const InputDecoration(
65 | border: OutlineInputBorder(),
66 | labelText: 'Enter a video id',
67 | ),
68 | controller: _videoIdTextEditingController,
69 | onSubmitted: (value) async {
70 | buildVideoOptions();
71 | },
72 | ),
73 | ),
74 | Padding(
75 | padding: const EdgeInsets.all(30.0),
76 | child: TextField(
77 | decoration: const InputDecoration(
78 | border: OutlineInputBorder(),
79 | labelText:
80 | 'Enter a token (leave empty if the video is public)',
81 | ),
82 | controller: _tokenTextEditingController,
83 | onSubmitted: (value) async {
84 | buildVideoOptions();
85 | },
86 | ),
87 | ),
88 | _controller != null
89 | ? PlayerWidget(controller: _controller!)
90 | : const SizedBox.shrink(),
91 | ],
92 | ),
93 | ),
94 | ),
95 | );
96 | }),
97 | );
98 | }
99 | }
100 |
101 | class PlayerWidget extends StatefulWidget {
102 | const PlayerWidget({
103 | super.key,
104 | required this.controller,
105 | });
106 |
107 | final ApiVideoPlayerController controller;
108 |
109 | @override
110 | State createState() => _PlayerWidgetState();
111 | }
112 |
113 | class _PlayerWidgetState extends State {
114 | String _currentTime = 'Get current time';
115 | String _duration = 'Get duration';
116 | bool _hideControls = false;
117 |
118 | @override
119 | void initState() {
120 | super.initState();
121 | widget.controller.initialize();
122 | widget.controller.addListener(ApiVideoPlayerControllerEventsListener(
123 | onReady: () {
124 | setState(() {
125 | _duration = 'Get duration';
126 | });
127 | },
128 | ));
129 | }
130 |
131 | @override
132 | void dispose() {
133 | widget.controller.dispose();
134 | super.dispose();
135 | }
136 |
137 | void _toggleLooping() {
138 | widget.controller.isLooping.then(
139 | (bool isLooping) {
140 | widget.controller.setIsLooping(!isLooping);
141 | ScaffoldMessenger.of(context).showSnackBar(
142 | SnackBar(
143 | content: Text(
144 | 'Your video is ${isLooping ? 'not on loop anymore' : 'on loop'}.',
145 | ),
146 | backgroundColor: Colors.blueAccent,
147 | ),
148 | );
149 | },
150 | );
151 | }
152 |
153 | @override
154 | Widget build(BuildContext context) {
155 | return Column(
156 | children: [
157 | SizedBox(
158 | width: 300.0,
159 | height: 300.0,
160 | child: _hideControls
161 | ? ApiVideoPlayer.noControls(controller: widget.controller)
162 | : ApiVideoPlayer(
163 | controller: widget.controller,
164 | style: PlayerStyle.defaultStyle),
165 | ),
166 | Row(mainAxisAlignment: MainAxisAlignment.center, children: [
167 | IconButton(
168 | icon: const Icon(Icons.replay_10),
169 | onPressed: () {
170 | widget.controller.seek(const Duration(seconds: -10));
171 | },
172 | ),
173 | IconButton(
174 | icon: const Icon(Icons.play_arrow),
175 | onPressed: () {
176 | widget.controller.play();
177 | },
178 | ),
179 | IconButton(
180 | icon: const Icon(Icons.pause),
181 | onPressed: () {
182 | widget.controller.pause();
183 | },
184 | ),
185 | IconButton(
186 | icon: const Icon(Icons.forward_10),
187 | onPressed: () {
188 | widget.controller.seek(const Duration(seconds: 10));
189 | },
190 | ),
191 | ]),
192 | Row(
193 | mainAxisAlignment: MainAxisAlignment.center,
194 | children: [
195 | IconButton(
196 | icon: const Icon(Icons.volume_off),
197 | onPressed: () {
198 | widget.controller.setIsMuted(true);
199 | },
200 | ),
201 | IconButton(
202 | icon: const Icon(Icons.volume_up),
203 | onPressed: () {
204 | widget.controller.setIsMuted(false);
205 | },
206 | ),
207 | IconButton(
208 | icon: const Icon(Icons.loop),
209 | onPressed: () => _toggleLooping(),
210 | ),
211 | ],
212 | ),
213 | TextButton(
214 | child: Text(
215 | _duration,
216 | textAlign: TextAlign.center,
217 | ),
218 | onPressed: () async {
219 | final Duration duration = await widget.controller.duration;
220 | setState(() {
221 | _duration = 'Duration: $duration';
222 | });
223 | },
224 | ),
225 | TextButton(
226 | child: Text(_currentTime),
227 | onPressed: () async {
228 | final Duration currentTime = await widget.controller.currentTime;
229 | setState(() {
230 | _currentTime = 'Get current time: $currentTime';
231 | });
232 | },
233 | ),
234 | TextButton(
235 | child: Text('${_hideControls ? 'Show' : 'Hide'} controls'),
236 | onPressed: () => setState(() {
237 | _hideControls = !_hideControls;
238 | }),
239 | ),
240 | ],
241 | );
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: apivideo_player_example
2 | description: Demonstrates how to use the apivideo_player plugin.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `flutter 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.17.1 <3.0.0"
10 |
11 | # Dependencies specify other packages that your package needs in order to work.
12 | # To automatically upgrade your package dependencies to the latest versions
13 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
14 | # dependencies can be manually updated by changing the version numbers below to
15 | # the latest version available on pub.dev. To see which dependencies have newer
16 | # versions available, run `flutter pub outdated`.
17 | dependencies:
18 | flutter:
19 | sdk: flutter
20 | apivideo_player:
21 | # When depending on this package from a real application you should use:
22 | # apivideo_player: ^x.y.z
23 | # See https://dart.dev/tools/pub/dependencies#version-constraints
24 | # The example app is bundled with the plugin so we use a path dependency on
25 | # the parent directory to use the current plugin's version.
26 | path: ../
27 |
28 | # The following adds the Cupertino Icons font to your application.
29 | # Use with the CupertinoIcons class for iOS style icons.
30 | cupertino_icons: ^1.0.2
31 |
32 | dev_dependencies:
33 | flutter_test:
34 | sdk: flutter
35 |
36 | # The "flutter_lints" package below contains a set of recommended lints to
37 | # encourage good coding practices. The lint set provided by the package is
38 | # activated in the `analysis_options.yaml` file located at the root of your
39 | # package. See that file for information about deactivating specific lint
40 | # rules and activating additional ones.
41 | flutter_lints: ^4.0.0
42 |
43 | # For information on the generic Dart part of this file, see the
44 | # following page: https://dart.dev/tools/pub/pubspec
45 |
46 | # The following section is specific to Flutter packages.
47 | flutter:
48 |
49 | # The following line ensures that the Material Icons font is
50 | # included with your application, so that you can use the icons in
51 | # the material Icons class.
52 | uses-material-design: true
53 |
54 | # To add assets to your application, add an assets section, like this:
55 | # assets:
56 | # - images/a_dot_burr.jpeg
57 | # - images/a_dot_ham.jpeg
58 |
59 | # An image asset can refer to one or more resolution-specific "variants", see
60 | # https://flutter.dev/assets-and-images/#resolution-aware
61 |
62 | # For details regarding adding assets from package dependencies, see
63 | # https://flutter.dev/assets-and-images/#from-packages
64 |
65 | # To add custom fonts to your application, add a fonts section here,
66 | # in this "flutter" section. Each entry in this list should have a
67 | # "family" key with the font family name, and a "fonts" key with a
68 | # list giving the asset and other descriptors for the font. For
69 | # example:
70 | # fonts:
71 | # - family: Schyler
72 | # fonts:
73 | # - asset: fonts/Schyler-Regular.ttf
74 | # - asset: fonts/Schyler-Italic.ttf
75 | # style: italic
76 | # - family: Trajan Pro
77 | # fonts:
78 | # - asset: fonts/TrajanPro.ttf
79 | # - asset: fonts/TrajanPro_Bold.ttf
80 | # weight: 700
81 | #
82 | # For details regarding fonts from package dependencies,
83 | # see https://flutter.dev/custom-fonts/#from-packages
84 |
--------------------------------------------------------------------------------
/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 in the flutter_test package. 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 | import 'package:apivideo_player_example/main.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_test/flutter_test.dart';
11 |
12 | void main() {
13 | testWidgets('Verify Platform version', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(const MyApp());
16 |
17 | // Verify that platform version is retrieved.
18 | expect(
19 | find.byWidgetPredicate(
20 | (Widget widget) =>
21 | widget is Text && widget.data!.startsWith('Running on:'),
22 | ),
23 | findsOneWidget,
24 | );
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | FltPlayer
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FltPlayer",
3 | "short_name": "FltPlayer",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "Demonstrates how to use the apivideo_player plugin.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/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/ephemeral/
38 | /Flutter/flutter_export_environment.sh
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apivideo/api.video-flutter-player/47305c3da9bc7e4e71073cb6a28c82793a048d1b/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/ios/Classes/ApiVideoPlayerPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface ApiVideoPlayerPlugin : NSObject
4 | @end
5 |
--------------------------------------------------------------------------------
/ios/Classes/ApiVideoPlayerPlugin.m:
--------------------------------------------------------------------------------
1 | #import "ApiVideoPlayerPlugin.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 "apivideo_player-Swift.h"
9 | #endif
10 |
11 | @implementation ApiVideoPlayerPlugin
12 | + (void)registerWithRegistrar:(NSObject*)registrar {
13 | [SwiftApiVideoPlayerPlugin registerWithRegistrar:registrar];
14 | }
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterPlayerController.swift:
--------------------------------------------------------------------------------
1 | import ApiVideoPlayer
2 | import Foundation
3 |
4 | // Manages a list of multiple players
5 | class FlutterPlayerController {
6 | private let binaryMessenger: FlutterBinaryMessenger
7 | private let textureRegistry: FlutterTextureRegistry
8 | private var players: [Int64: FlutterPlayerView] = [:]
9 |
10 | init(binaryMessenger: FlutterBinaryMessenger, textureRegistry: FlutterTextureRegistry) {
11 | self.binaryMessenger = binaryMessenger
12 | self.textureRegistry = textureRegistry
13 | }
14 |
15 | func isCreated(textureId: Int64) -> Bool {
16 | return players[textureId] != nil
17 | }
18 |
19 | func initialize(autoplay: Bool) -> Int64 {
20 | let player = FlutterPlayerView(binaryMessenger: binaryMessenger, textureRegistry: textureRegistry, autoplay: autoplay)
21 |
22 | players[player.textureId] = player
23 |
24 | return player.textureId
25 | }
26 |
27 | func dispose(textureId: Int64) {
28 | guard let player = players[textureId] else {
29 | print("Unknown player \(textureId)")
30 | return
31 | }
32 | player.dispose()
33 | players.removeValue(forKey: textureId)
34 | }
35 |
36 | func disposeAll() {
37 | for player in players.values {
38 | player.dispose()
39 | }
40 | players.removeAll()
41 | }
42 |
43 | func isPlaying(textureId: Int64) -> Bool {
44 | guard let player = players[textureId] else {
45 | print("Unknown player \(textureId)")
46 | return false
47 | }
48 | return player.isPlaying
49 | }
50 |
51 | func isLive(textureId: Int64) -> Bool {
52 | guard let player = players[textureId] else {
53 | print("Unknown player \(textureId)")
54 | return false
55 | }
56 | return player.isLive
57 | }
58 |
59 | func getCurrentTime(textureId: Int64) -> Int {
60 | guard let player = players[textureId] else {
61 | print("Unknown player \(textureId)")
62 | return 0
63 | }
64 | return player.currentTime.toMs()
65 | }
66 |
67 | func setCurrentTime(textureId: Int64, currentTime: Int) {
68 | guard let player = players[textureId] else {
69 | print("Unknown player \(textureId)")
70 | return
71 | }
72 | player.currentTime = currentTime.msToCMTime()
73 | }
74 |
75 | func getDuration(textureId: Int64) -> Int {
76 | guard let player = players[textureId] else {
77 | print("Unknown player \(textureId)")
78 | return 0
79 | }
80 | return player.duration.toMs()
81 | }
82 |
83 | func getVideoOptions(textureId: Int64) -> VideoOptions? {
84 | guard let player = players[textureId] else {
85 | print("Unknown player \(textureId)")
86 | return nil
87 | }
88 | return player.videoOptions
89 | }
90 |
91 | func setVideoOptions(textureId: Int64, videoOptions: VideoOptions) {
92 | guard let player = players[textureId] else {
93 | print("Unknown player \(textureId)")
94 | return
95 | }
96 | player.videoOptions = videoOptions
97 | }
98 |
99 | func getAutoplay(textureId: Int64) -> Bool {
100 | guard let player = players[textureId] else {
101 | print("Unknown player \(textureId)")
102 | return false
103 | }
104 | return player.autoplay
105 | }
106 |
107 | func setAutoplay(textureId: Int64, autoplay: Bool) {
108 | guard let player = players[textureId] else {
109 | print("Unknown player \(textureId)")
110 | return
111 | }
112 | player.autoplay = autoplay
113 | }
114 |
115 | func getIsMuted(textureId: Int64) -> Bool {
116 | guard let player = players[textureId] else {
117 | print("Unknown player \(textureId)")
118 | return false
119 | }
120 | return player.isMuted
121 | }
122 |
123 | func setIsMuted(textureId: Int64, isMuted: Bool) {
124 | guard let player = players[textureId] else {
125 | print("Unknown player \(textureId)")
126 | return
127 | }
128 | player.isMuted = isMuted
129 | }
130 |
131 | func getIsLooping(textureId: Int64) -> Bool {
132 | guard let player = players[textureId] else {
133 | print("Unknown player \(textureId)")
134 | return false
135 | }
136 | return player.isLooping
137 | }
138 |
139 | func setIsLooping(textureId: Int64, isLooping: Bool) {
140 | guard let player = players[textureId] else {
141 | print("Unknown player \(textureId)")
142 | return
143 | }
144 | player.isLooping = isLooping
145 | }
146 |
147 | func getVolume(textureId: Int64) -> Float {
148 | guard let player = players[textureId] else {
149 | print("Unknown player \(textureId)")
150 | return 0
151 | }
152 | return player.volume
153 | }
154 |
155 | func setVolume(textureId: Int64, volume: Float) {
156 | guard let player = players[textureId] else {
157 | print("Unknown player \(textureId)")
158 | return
159 | }
160 | player.volume = volume
161 | }
162 |
163 | func getVideoSize(textureId: Int64) -> CGSize? {
164 | guard let player = players[textureId] else {
165 | print("Unknown player \(textureId)")
166 | return nil
167 | }
168 | return player.videoSize
169 | }
170 |
171 | func play(textureId: Int64) {
172 | guard let player = players[textureId] else {
173 | print("Unknown player \(textureId)")
174 | return
175 | }
176 | player.play()
177 | }
178 |
179 | func pause(textureId: Int64) {
180 | guard let player = players[textureId] else {
181 | print("Unknown player \(textureId)")
182 | return
183 | }
184 | player.pause()
185 | }
186 |
187 | func seek(textureId: Int64, offset: Int) {
188 | guard let player = players[textureId] else {
189 | print("Unknown player \(textureId)")
190 | return
191 | }
192 | player.seek(offset: offset.msToCMTime())
193 | }
194 |
195 | func setPlaybackSpeed(textureId: Int64, speedRate: Double) {
196 | guard let player = players[textureId] else {
197 | print("Unknown player \(textureId)")
198 | return
199 | }
200 | player.speedRate = Float(speedRate)
201 | }
202 |
203 | func getPlaybackSpeed(textureId: Int64) -> Double {
204 | guard let player = players[textureId] else {
205 | print("Unknown player \(textureId)")
206 | return 0
207 | }
208 | return Double(player.speedRate)
209 | }
210 | }
211 |
212 | extension Int {
213 | func msToCMTime() -> CMTime {
214 | return CMTime(value: CMTimeValue(self), timescale: 1000)
215 | }
216 | }
217 |
218 | extension CMTime {
219 | func toMs() -> Int {
220 | let seconds = self.seconds
221 | guard !(seconds.isNaN || seconds.isInfinite) else {
222 | return 0
223 | }
224 | return Int(seconds * 1000)
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterPlayerView.swift:
--------------------------------------------------------------------------------
1 | import ApiVideoPlayer
2 | import AVFoundation
3 | import AVKit
4 | import Foundation
5 |
6 | class FlutterPlayerView: NSObject, FlutterStreamHandler {
7 | private let playerTexture = PlayerFlutterTexture()
8 | private let textureRegistry: FlutterTextureRegistry
9 | let textureId: Int64!
10 | private let frameUpdater: FrameUpdater
11 | private var displayLink: CADisplayLink!
12 | private let playerController: ApiVideoPlayerController
13 | private let playerLayer = AVPlayerLayer() // Only use to fix bugs according to flutter video_player plugin
14 |
15 | private let eventChannel: FlutterEventChannel
16 | private var eventSink: FlutterEventSink?
17 |
18 | init(binaryMessenger: FlutterBinaryMessenger,
19 | textureRegistry: FlutterTextureRegistry,
20 | videoOptions: VideoOptions? = nil,
21 | autoplay: Bool)
22 | {
23 | self.textureRegistry = textureRegistry
24 | playerController = ApiVideoPlayerController(videoOptions: videoOptions, playerLayer: playerLayer, autoplay: autoplay)
25 | textureId = self.textureRegistry.register(playerTexture)
26 | frameUpdater = FrameUpdater(textureRegistry: self.textureRegistry, textureId: textureId)
27 | displayLink = FlutterPlayerView.createDisplayLink(frameUpdater: frameUpdater)
28 | eventChannel = FlutterEventChannel(name: "video.api.player/events\(String(textureId))", binaryMessenger: binaryMessenger)
29 | super.init()
30 | eventChannel.setStreamHandler(self)
31 | playerController.addDelegate(delegate: self)
32 | }
33 |
34 | static func createVideoOutput() -> AVPlayerItemVideoOutput {
35 | let pixBuffAttributes = [kCVPixelBufferPixelFormatTypeKey: Int(kCVPixelFormatType_32BGRA),
36 | kCVPixelBufferIOSurfacePropertiesKey: [:]] as [String: Any]
37 | return AVPlayerItemVideoOutput(pixelBufferAttributes: pixBuffAttributes)
38 | }
39 |
40 | static func createDisplayLink(frameUpdater: FrameUpdater) -> CADisplayLink {
41 | let displayLink = CADisplayLink(target: frameUpdater, selector: #selector(FrameUpdater.onDisplayLink(_:)))
42 | displayLink.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
43 | displayLink.isPaused = true
44 | return displayLink
45 | }
46 |
47 | var videoOptions: VideoOptions? {
48 | get {
49 | playerController.videoOptions
50 | }
51 | set {
52 | playerController.videoOptions = newValue
53 | }
54 | }
55 |
56 | var isPlaying: Bool {
57 | playerController.isPlaying
58 | }
59 |
60 | var isLive: Bool {
61 | playerController.isLive
62 | }
63 |
64 | var duration: CMTime {
65 | playerController.duration
66 | }
67 |
68 | var currentTime: CMTime {
69 | get {
70 | playerController.currentTime
71 | }
72 | set {
73 | eventSink?(["type": "seekStarted"])
74 | playerController.seek(to: newValue)
75 | }
76 | }
77 |
78 | var autoplay: Bool {
79 | get {
80 | playerController.autoplay
81 | }
82 | set {
83 | playerController.autoplay = newValue
84 | }
85 | }
86 |
87 | var isMuted: Bool {
88 | get {
89 | playerController.isMuted
90 | }
91 | set {
92 | playerController.isMuted = newValue
93 | }
94 | }
95 |
96 | var volume: Float {
97 | get {
98 | playerController.volume
99 | }
100 | set {
101 | playerController.volume = newValue
102 | }
103 | }
104 |
105 | var isLooping: Bool {
106 | get {
107 | playerController.isLooping
108 | }
109 | set {
110 | playerController.isLooping = newValue
111 | }
112 | }
113 |
114 | var speedRate: Float {
115 | get {
116 | playerController.speedRate
117 | }
118 | set {
119 | playerController.speedRate = newValue
120 | }
121 | }
122 |
123 | var videoSize: CGSize? {
124 | let videoSize = playerController.videoSize
125 | if videoSize.width != 0, videoSize.height != 0 {
126 | return videoSize
127 | } else {
128 | return nil
129 | }
130 | }
131 |
132 | func play() {
133 | playerController.play()
134 | }
135 |
136 | func pause() {
137 | playerController.pause()
138 | }
139 |
140 | func seek(offset: CMTime) {
141 | eventSink?(["type": "seekStarted"])
142 | playerController.seek(offset: offset)
143 | textureRegistry.textureFrameAvailable(textureId) // render frame of the new scene
144 | }
145 |
146 | func onTextureUnregistered() {
147 | dispose()
148 | }
149 |
150 | func dispose() {
151 | pause()
152 | playerController.removeOutput(output: playerTexture.videoOutput)
153 | displayLink.invalidate()
154 | textureRegistry.unregisterTexture(textureId)
155 | eventSink?(FlutterEndOfEventStream)
156 | }
157 |
158 | func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
159 | eventSink = events
160 | return nil
161 | }
162 |
163 | func onCancel(withArguments _: Any?) -> FlutterError? {
164 | eventSink = nil
165 | return nil
166 | }
167 | }
168 |
169 | extension FlutterPlayerView: ApiVideoPlayerControllerPlayerDelegate {
170 | func didPrepare() {}
171 |
172 | func didReady() {
173 | playerController.addOutput(output: playerTexture.videoOutput)
174 | // Hack to load the first image. We don't need it in case of autoplay
175 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
176 | self.textureRegistry.textureFrameAvailable(self.textureId)
177 | }
178 |
179 | eventSink?(["type": "ready"])
180 | }
181 |
182 | func didPause() {
183 | displayLink.isPaused = true
184 | eventSink?(["type": "paused"])
185 | }
186 |
187 | func didPlay() {
188 | displayLink.isPaused = false
189 | eventSink?(["type": "played"])
190 | }
191 |
192 | func didReplay() {}
193 |
194 | func didMute() {}
195 |
196 | func didUnMute() {}
197 |
198 | func didLoop() {}
199 |
200 | func didSetVolume(_: Float) {}
201 |
202 | func didSeek(_: CMTime, _: CMTime) {
203 | eventSink?(["type": "seek"])
204 | }
205 |
206 | func didEnd() {
207 | displayLink.isPaused = true
208 | eventSink?(["type": "ended"])
209 | }
210 |
211 | func didError(_ error: Error) {
212 | eventSink?(FlutterError(code: "error", message: error.localizedDescription, details: "Stacktrace: \(Thread.callStackSymbols)"))
213 | }
214 |
215 | func didVideoSizeChanged(_: CGSize) {}
216 | }
217 |
218 | class FrameUpdater: NSObject {
219 | private let textureRegistry: FlutterTextureRegistry
220 | private let textureId: Int64
221 |
222 | init(textureRegistry: FlutterTextureRegistry, textureId: Int64) {
223 | self.textureRegistry = textureRegistry
224 | self.textureId = textureId
225 | }
226 |
227 | @objc func onDisplayLink(_: CADisplayLink) {
228 | textureRegistry.textureFrameAvailable(textureId)
229 | }
230 | }
231 |
232 | class PlayerFlutterTexture: NSObject, FlutterTexture {
233 | let videoOutput = FlutterPlayerView.createVideoOutput()
234 |
235 | func copyPixelBuffer() -> Unmanaged? {
236 | let outputItemTime = videoOutput.itemTime(forHostTime: CACurrentMediaTime())
237 | if videoOutput.hasNewPixelBuffer(forItemTime: outputItemTime) {
238 | guard let pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: outputItemTime, itemTimeForDisplay: nil) else {
239 | return nil
240 | }
241 | return Unmanaged.passRetained(pixelBuffer)
242 | } else {
243 | return nil
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/ios/Classes/SwiftApiVideoPlayerPlugin.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 |
4 | public class SwiftApiVideoPlayerPlugin: NSObject, FlutterPlugin {
5 | private var textureRegistry: FlutterTextureRegistry!
6 | private var messenger: FlutterBinaryMessenger!
7 | private var api: MethodCallHandler!
8 |
9 | public static func register(with registrar: FlutterPluginRegistrar) {
10 | let instance = SwiftApiVideoPlayerPlugin(registrar: registrar)
11 | registrar.publish(instance)
12 | }
13 |
14 | init(registrar: FlutterPluginRegistrar) {
15 | textureRegistry = registrar.textures()
16 | messenger = registrar.messenger()
17 | api = MethodCallHandler(binaryMessenger: messenger, controller: FlutterPlayerController(binaryMessenger: messenger, textureRegistry: textureRegistry))
18 | }
19 |
20 | public func detachFromEngine(for _: FlutterPluginRegistrar) {
21 | api.dispose()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/apivideo_player.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 | # Run `pod lib lint apivideo_player.podspec` to validate before publishing.
4 | #
5 | Pod::Spec.new do |s|
6 | s.name = 'apivideo_player'
7 | s.version = '0.0.1'
8 | s.summary = 'A new Flutter plugin project.'
9 | s.description = <<-DESC
10 | A new Flutter plugin project.
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.dependency 'ApiVideoPlayer', "1.3.0"
19 | s.platform = :ios, '11.0'
20 |
21 | # Flutter.framework does not contain a i386 slice.
22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
23 | s.swift_version = '5.0'
24 | end
25 |
--------------------------------------------------------------------------------
/lib/apivideo_player.dart:
--------------------------------------------------------------------------------
1 | export 'src/apivideo_player_controller.dart';
2 | export 'src/apivideo_types.dart';
3 | export 'src/platform/apivideo_mobile_player_platform.dart';
4 | export 'src/style/apivideo_style.dart';
5 | export 'src/widgets/apivideo_player.dart';
6 | export 'src/widgets/apivideo_player_controls_bar.dart';
7 | export 'src/widgets/apivideo_player_opacity.dart';
8 | export 'src/widgets/apivideo_player_overlay.dart';
9 | export 'src/widgets/apivideo_player_settings_bar.dart';
10 | export 'src/widgets/apivideo_player_time_slider.dart';
11 | export 'src/widgets/apivideo_player_video.dart';
12 |
--------------------------------------------------------------------------------
/lib/src/apivideo_player_controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:apivideo_player/apivideo_player.dart';
4 | import 'package:apivideo_player/src/apivideo_player_life_cycle_observer.dart';
5 | import 'package:apivideo_player/src/apivideo_types.dart';
6 | import 'package:flutter/services.dart';
7 | import 'package:meta/meta.dart';
8 |
9 | import 'platform/apivideo_player_platform_interface.dart';
10 |
11 | ApiVideoPlayerPlatform get _playerPlatform {
12 | return ApiVideoPlayerPlatform.instance;
13 | }
14 |
15 | class ApiVideoPlayerController {
16 | final VideoOptions _initialVideoOptions;
17 | final bool _initialAutoplay;
18 |
19 | static const int kUninitializedTextureId = -1;
20 | int _textureId = kUninitializedTextureId;
21 |
22 | StreamSubscription? _eventSubscription;
23 | final List _eventsListeners = [];
24 | final List _widgetListeners = [];
25 |
26 | PlayerLifeCycleObserver? _lifeCycleObserver;
27 |
28 | /// This is just exposed for testing. Do not use it.
29 | @internal
30 | int get textureId => _textureId;
31 |
32 | /// Creates a new controller where each event callbacks are set explicitly.
33 | ApiVideoPlayerController({
34 | required VideoOptions videoOptions,
35 | bool autoplay = false,
36 | VoidCallback? onReady,
37 | VoidCallback? onPlay,
38 | VoidCallback? onPause,
39 | VoidCallback? onEnd,
40 | Function(Object)? onError,
41 | }) : _initialAutoplay = autoplay,
42 | _initialVideoOptions = videoOptions {
43 | _eventsListeners.add(ApiVideoPlayerControllerEventsListener(
44 | onReady: onReady,
45 | onPlay: onPlay,
46 | onPause: onPause,
47 | onEnd: onEnd,
48 | onError: onError));
49 | }
50 |
51 | /// Creates a new controller with a [ApiVideoPlayerControllerEventsListener].
52 | ApiVideoPlayerController.fromListener(
53 | {required VideoOptions videoOptions,
54 | bool autoplay = false,
55 | ApiVideoPlayerControllerEventsListener? listener})
56 | : _initialAutoplay = autoplay,
57 | _initialVideoOptions = videoOptions {
58 | if (listener != null) {
59 | _eventsListeners.add(listener);
60 | }
61 | }
62 |
63 | /// Whether the controller has been created.
64 | Future get isCreated => _playerPlatform.isCreated(_textureId);
65 |
66 | /// Whether the video is playing.
67 | Future get isPlaying {
68 | return _playerPlatform.isPlaying(_textureId);
69 | }
70 |
71 | /// Whether the current video is a live.
72 | Future get isLive async {
73 | return await _playerPlatform.isLive(_textureId);
74 | }
75 |
76 | /// The video current time.
77 | Future get currentTime async {
78 | final milliseconds = await _playerPlatform.getCurrentTime(_textureId);
79 | return Duration(milliseconds: milliseconds);
80 | }
81 |
82 | /// Sets the current playback time.
83 | Future setCurrentTime(Duration currentTime) {
84 | return _playerPlatform.setCurrentTime(
85 | _textureId, currentTime.inMilliseconds);
86 | }
87 |
88 | /// The duration of the video.
89 | Future get duration async {
90 | final milliseconds = await _playerPlatform.getDuration(_textureId);
91 | return Duration(milliseconds: milliseconds);
92 | }
93 |
94 | /// The current video options.
95 | Future get videoOptions {
96 | return _playerPlatform.getVideoOptions(_textureId);
97 | }
98 |
99 | /// Sets the video options to play a new video.
100 | Future setVideoOptions(VideoOptions videoOptions) {
101 | return _playerPlatform.setVideoOptions(_textureId, videoOptions);
102 | }
103 |
104 | /// Whether the video will be play automatically.
105 | Future get autoplay {
106 | return _playerPlatform.getAutoplay(_textureId);
107 | }
108 |
109 | /// Sets if the video will be play automatically.
110 | Future setAutoplay(bool autoplay) {
111 | return _playerPlatform.setAutoplay(_textureId, autoplay);
112 | }
113 |
114 | /// Whether the video is muted.
115 | Future get isMuted {
116 | return _playerPlatform.getIsMuted(_textureId);
117 | }
118 |
119 | /// Mutes/unmutes the video.
120 | Future setIsMuted(bool isMuted) {
121 | return _playerPlatform.setIsMuted(_textureId, isMuted);
122 | }
123 |
124 | /// Whether the video is in loop mode.
125 | Future get isLooping {
126 | return _playerPlatform.getIsLooping(_textureId);
127 | }
128 |
129 | /// Sets if the video should be played in loop.
130 | Future setIsLooping(bool isLooping) {
131 | return _playerPlatform.setIsLooping(_textureId, isLooping);
132 | }
133 |
134 | /// The current audio volume
135 | Future get volume {
136 | return _playerPlatform.getVolume(_textureId);
137 | }
138 |
139 | /// Sets the audio volume.
140 | ///
141 | /// From 0 to 1 (0 = muted, 1 = 100%).
142 | Future setVolume(double volume) {
143 | if (volume < 0 || volume > 1) {
144 | throw ArgumentError('Volume must be between 0 and 1');
145 | }
146 | return _playerPlatform.setVolume(_textureId, volume);
147 | }
148 |
149 | /// The playback speed rate.
150 | Future get speedRate {
151 | return _playerPlatform.getPlaybackRate(_textureId);
152 | }
153 |
154 | /// Sets the playback speed rate.
155 | ///
156 | /// We recommend to set the value from 0.5 to 2 (0.5 = 50%, 2 = 200%).
157 | Future setSpeedRate(double speedRate) {
158 | return _playerPlatform.setPlaybackRate(_textureId, speedRate);
159 | }
160 |
161 | /// The current video size.
162 | Future get videoSize {
163 | return _playerPlatform.getVideoSize(_textureId);
164 | }
165 |
166 | /// Initializes the controller.
167 | Future initialize() async {
168 | _textureId = await _playerPlatform.initialize(_initialAutoplay) ??
169 | kUninitializedTextureId;
170 |
171 | _lifeCycleObserver = PlayerLifeCycleObserver(this);
172 | _lifeCycleObserver?.initialize();
173 |
174 | _eventSubscription = _playerPlatform
175 | .playerEventsFor(_textureId)
176 | .listen(_eventListener, onError: _errorListener);
177 |
178 | await _playerPlatform.create(_textureId, _initialVideoOptions);
179 |
180 | for (var listener in [..._widgetListeners]) {
181 | if (listener.onTextureReady != null) {
182 | listener.onTextureReady!();
183 | }
184 | }
185 |
186 | return;
187 | }
188 |
189 | /// Plays the video.
190 | Future play() {
191 | return _playerPlatform.play(_textureId);
192 | }
193 |
194 | /// Pauses the video.
195 | Future pause() {
196 | return _playerPlatform.pause(_textureId);
197 | }
198 |
199 | /// Disposes the controller.
200 | Future dispose() async {
201 | await _eventSubscription?.cancel();
202 | _eventsListeners.clear();
203 | await _playerPlatform.dispose(_textureId);
204 | _lifeCycleObserver?.dispose();
205 | return;
206 | }
207 |
208 | /// Adds/substracts the given Duration to/from the playback time.
209 | Future seek(Duration offset) {
210 | return _playerPlatform.seek(_textureId, offset.inMilliseconds);
211 | }
212 |
213 | /// Adds an event listener to this controller.
214 | ///
215 | /// ```dart
216 | /// final ApiVideoPlayerControllerEventsListener _eventsListener =
217 | /// ApiVideoPlayerControllerEventsListener(onPlay: () => print('PLAY'));
218 | ///
219 | /// controller.addEventsListener(_eventsListener);
220 | /// ```
221 | void addListener(ApiVideoPlayerControllerEventsListener listener) {
222 | _eventsListeners.add(listener);
223 | }
224 |
225 | /// Adds an event listener to this controller.
226 | ///
227 | /// ```dart
228 | /// final ApiVideoPlayerControllerEventsListener _eventsListener =
229 | /// ApiVideoPlayerControllerEventsListener(onPlay: () => print('PLAY'));
230 | ///
231 | /// controller.removeEventsListener(_eventsListener);
232 | /// ```
233 | void removeListener(ApiVideoPlayerControllerEventsListener listener) {
234 | _eventsListeners.remove(listener);
235 | }
236 |
237 | /// Internal use only. Do not use it.
238 | @internal
239 | void addWidgetListener(ApiVideoPlayerControllerWidgetListener listener) {
240 | _widgetListeners.add(listener);
241 | }
242 |
243 | /// Internal use only. Do not use it.
244 | @internal
245 | void removeWidgetListener(ApiVideoPlayerControllerWidgetListener listener) {
246 | _widgetListeners.remove(listener);
247 | }
248 |
249 | void _errorListener(Object obj) {
250 | final PlatformException e = obj as PlatformException;
251 | for (var listener in [..._eventsListeners]) {
252 | if (listener.onError != null) {
253 | listener.onError!(e);
254 | }
255 | }
256 | }
257 |
258 | void _eventListener(PlayerEvent event) {
259 | switch (event.type) {
260 | case PlayerEventType.ready:
261 | for (var listener in [..._eventsListeners]) {
262 | if (listener.onReady != null) {
263 | listener.onReady!();
264 | }
265 | }
266 | break;
267 | case PlayerEventType.played:
268 | for (var listener in [..._eventsListeners]) {
269 | if (listener.onPlay != null) {
270 | listener.onPlay!();
271 | }
272 | }
273 | break;
274 | case PlayerEventType.paused:
275 | for (var listener in [..._eventsListeners]) {
276 | if (listener.onPause != null) {
277 | listener.onPause!();
278 | }
279 | }
280 | break;
281 | case PlayerEventType.seek:
282 | for (var listener in [..._eventsListeners]) {
283 | if (listener.onSeek != null) {
284 | listener.onSeek!();
285 | }
286 | }
287 | break;
288 | case PlayerEventType.seekStarted:
289 | for (var listener in [..._eventsListeners]) {
290 | if (listener.onSeekStarted != null) {
291 | listener.onSeekStarted!();
292 | }
293 | }
294 | break;
295 | case PlayerEventType.ended:
296 | for (var listener in [..._eventsListeners]) {
297 | if (listener.onEnd != null) {
298 | listener.onEnd!();
299 | }
300 | }
301 | break;
302 | case PlayerEventType.unknown:
303 | // Nothing to do
304 | break;
305 | }
306 | }
307 | }
308 |
309 | /// The controller events listener.
310 | /// Use this to listen to the player events.
311 | class ApiVideoPlayerControllerEventsListener {
312 | final VoidCallback? onReady;
313 | final VoidCallback? onPlay;
314 | final VoidCallback? onPause;
315 | final VoidCallback? onSeek;
316 | final VoidCallback? onSeekStarted;
317 | final VoidCallback? onEnd;
318 | final Function(Object)? onError;
319 |
320 | ApiVideoPlayerControllerEventsListener(
321 | {this.onReady,
322 | this.onPlay,
323 | this.onPause,
324 | this.onSeek,
325 | this.onSeekStarted,
326 | this.onEnd,
327 | this.onError});
328 | }
329 |
330 | /// The internal controller widget listener.
331 | /// Uses by the [ApiVideoPlayerController] to notify the widget when the texture is ready.
332 | /// Only to be used in the Widget that hold the video such as [ApiVideoPlayerVideo].
333 | class ApiVideoPlayerControllerWidgetListener {
334 | final VoidCallback? onTextureReady;
335 |
336 | ApiVideoPlayerControllerWidgetListener({this.onTextureReady});
337 | }
338 |
--------------------------------------------------------------------------------
/lib/src/apivideo_player_life_cycle_observer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 |
3 | import '../apivideo_player.dart';
4 |
5 | /// The player life cycle observer.
6 | /// It pauses the player when the app is paused.
7 | /// It resumes the player when the app is resumed (if the player was playing before).
8 | class PlayerLifeCycleObserver extends Object with WidgetsBindingObserver {
9 | final ApiVideoPlayerController controller;
10 | bool _wasPlayingBeforePause = false;
11 |
12 | PlayerLifeCycleObserver(this.controller);
13 |
14 | void initialize() {
15 | _ambiguate(WidgetsBinding.instance)!.addObserver(this);
16 | }
17 |
18 | @override
19 | void didChangeAppLifecycleState(AppLifecycleState state) async {
20 | switch (state) {
21 | case AppLifecycleState.resumed:
22 | if (_wasPlayingBeforePause) {
23 | controller.play();
24 | }
25 | break;
26 | case AppLifecycleState.paused:
27 | pause();
28 | break;
29 | default:
30 | break;
31 | }
32 | }
33 |
34 | void pause() async {
35 | _wasPlayingBeforePause = await controller.isPlaying;
36 | controller.pause();
37 | }
38 |
39 | void dispose() {
40 | _ambiguate(WidgetsBinding.instance)!.removeObserver(this);
41 | }
42 | }
43 |
44 | T? _ambiguate(T? value) => value;
45 |
--------------------------------------------------------------------------------
/lib/src/apivideo_types.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'apivideo_types.g.dart';
4 |
5 | /// The video types enabled by api.video platform.
6 | enum VideoType {
7 | /// Video on demand.
8 | @JsonValue("vod")
9 | vod,
10 |
11 | /// Live video.
12 | @JsonValue("live")
13 | live
14 | }
15 |
16 | /// The video options that defines a video on api.video platform.
17 | @JsonSerializable()
18 | class VideoOptions {
19 | /// The video id or live stream id from api.video platform.
20 | String videoId;
21 |
22 | /// The video type. Either [VideoType.vod] or [VideoType.live].
23 | VideoType type;
24 |
25 | /// The private token if the video is private.
26 | String? token;
27 |
28 | /// Creates a [VideoOptions] object from a [videoId] a [type] and a [token].
29 | /// The [token] could be null.
30 | VideoOptions.raw({required this.videoId, required this.type, this.token});
31 |
32 | /// Creates a [VideoOptions] object from a [videoId] a [type] and a [token].
33 | /// If the [type] is null, it will be inferred from the [videoId]:
34 | /// * If the [videoId] starts with "vi", the type will be [VideoType.vod].
35 | /// * If the [videoId] starts with "li", the type will be [VideoType.live].
36 | /// The [token] could be null.
37 | factory VideoOptions(
38 | {required String videoId, VideoType? type, String? token}) {
39 | type ??= _inferType(videoId);
40 | return VideoOptions.raw(videoId: videoId, type: type, token: token);
41 | }
42 |
43 | /// Creates a [VideoOptions] from a [json] map.
44 | factory VideoOptions.fromJson(Map json) =>
45 | _$VideoOptionsFromJson(json);
46 |
47 | /// Creates a json map from a [VideoOptions].
48 | Map toJson() => _$VideoOptionsToJson(this);
49 |
50 | /// Infers the [VideoType] from a [videoId].
51 | /// If the [videoId] starts with "vi", the type will be [VideoType.vod].
52 | /// If the [videoId] starts with "li", the type will be [VideoType.live].
53 | static VideoType _inferType(String videoId) {
54 | if (videoId.startsWith("vi")) {
55 | return VideoType.vod;
56 | } else if (videoId.startsWith("li")) {
57 | return VideoType.live;
58 | } else {
59 | throw ArgumentError(
60 | "Failed to infer the video type from the videoId: $videoId");
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/src/apivideo_types.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'apivideo_types.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | VideoOptions _$VideoOptionsFromJson(Map json) => VideoOptions(
10 | videoId: json['videoId'] as String,
11 | type: $enumDecodeNullable(_$VideoTypeEnumMap, json['type']) ??
12 | VideoType.vod,
13 | token: json['token'] as String?,
14 | );
15 |
16 | Map _$VideoOptionsToJson(VideoOptions instance) =>
17 | {
18 | 'videoId': instance.videoId,
19 | 'type': _$VideoTypeEnumMap[instance.type]!,
20 | 'token': instance.token,
21 | };
22 |
23 | const _$VideoTypeEnumMap = {
24 | VideoType.vod: 'vod',
25 | VideoType.live: 'live',
26 | };
27 |
--------------------------------------------------------------------------------
/lib/src/platform/apivideo_mobile_player_platform.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/services.dart';
3 |
4 | import '../apivideo_types.dart';
5 | import 'apivideo_player_platform_interface.dart';
6 |
7 | /// The implementation of [ApiVideoPlayerPlatform] for mobile (Android and iOS).
8 | class ApiVideoMobilePlayer extends ApiVideoPlayerPlatform {
9 | final MethodChannel _channel =
10 | const MethodChannel('video.api.player/controller');
11 |
12 | /// Registers this class as the default instance of [PathProviderPlatform].
13 | static void registerWith() {
14 | ApiVideoPlayerPlatform.instance = ApiVideoMobilePlayer();
15 | }
16 |
17 | @override
18 | Future isCreated(int textureId) async {
19 | final Map reply =
20 | await _channel.invokeMapMethodWithTexture(
21 | 'isCreated', TextureMessage(textureId: textureId)) as Map;
22 | return reply['isCreated'] as bool;
23 | }
24 |
25 | @override
26 | Future isPlaying(int textureId) async {
27 | final Map reply =
28 | await _channel.invokeMapMethodWithTexture(
29 | 'isPlaying', TextureMessage(textureId: textureId)) as Map;
30 | return reply['isPlaying'] as bool;
31 | }
32 |
33 | @override
34 | Future isLive(int textureId) async {
35 | final Map reply =
36 | await _channel.invokeMapMethodWithTexture(
37 | 'isLive', TextureMessage(textureId: textureId)) as Map;
38 | return reply['isLive'] as bool;
39 | }
40 |
41 | @override
42 | Future getCurrentTime(int textureId) async {
43 | final Map reply =
44 | await _channel.invokeMapMethodWithTexture(
45 | 'getCurrentTime', TextureMessage(textureId: textureId)) as Map;
46 | return reply['currentTime'] as int;
47 | }
48 |
49 | @override
50 | Future setCurrentTime(int textureId, int currentTime) {
51 | final Map params = {
52 | "currentTime": currentTime
53 | };
54 | return _channel.invokeMapMethodWithTexture(
55 | 'setCurrentTime', TextureMessage(textureId: textureId), params);
56 | }
57 |
58 | @override
59 | Future getDuration(int textureId) async {
60 | final Map reply =
61 | await _channel.invokeMapMethodWithTexture(
62 | 'getDuration', TextureMessage(textureId: textureId)) as Map;
63 | return reply['duration'] as int;
64 | }
65 |
66 | @override
67 | Future getVideoOptions(int textureId) async {
68 | final Map reply =
69 | await _channel.invokeMapMethodWithTexture(
70 | 'getVideoOptions', TextureMessage(textureId: textureId))
71 | as Map;
72 | return VideoOptions.fromJson(reply);
73 | }
74 |
75 | @override
76 | Future setVideoOptions(int textureId, VideoOptions videoOptions) {
77 | final Map videoParams = {
78 | "videoOptions": videoOptions.toJson()
79 | };
80 | return _channel.invokeMapMethodWithTexture(
81 | 'setVideoOptions', TextureMessage(textureId: textureId), videoParams);
82 | }
83 |
84 | @override
85 | Future getAutoplay(int textureId) async {
86 | final Map reply =
87 | await _channel.invokeMapMethodWithTexture(
88 | 'getAutoplay', TextureMessage(textureId: textureId)) as Map;
89 | return reply['autoplay'] as bool;
90 | }
91 |
92 | @override
93 | Future setAutoplay(int textureId, bool autoplay) {
94 | final Map params = {"autoplay": autoplay};
95 | return _channel.invokeMapMethodWithTexture(
96 | 'setAutoplay', TextureMessage(textureId: textureId), params);
97 | }
98 |
99 | @override
100 | Future getIsMuted(int textureId) async {
101 | final Map reply =
102 | await _channel.invokeMapMethodWithTexture(
103 | 'getIsMuted', TextureMessage(textureId: textureId)) as Map;
104 | return reply['isMuted'] as bool;
105 | }
106 |
107 | @override
108 | Future setIsMuted(int textureId, bool isMuted) {
109 | final Map params = {"isMuted": isMuted};
110 | return _channel.invokeMapMethodWithTexture(
111 | 'setIsMuted', TextureMessage(textureId: textureId), params);
112 | }
113 |
114 | @override
115 | Future getIsLooping(int textureId) async {
116 | final Map reply =
117 | await _channel.invokeMapMethodWithTexture(
118 | 'getIsLooping', TextureMessage(textureId: textureId)) as Map;
119 | return reply['isLooping'] as bool;
120 | }
121 |
122 | @override
123 | Future setIsLooping(int textureId, bool isLooping) {
124 | final Map params = {
125 | "isLooping": isLooping
126 | };
127 | return _channel.invokeMapMethodWithTexture(
128 | 'setIsLooping', TextureMessage(textureId: textureId), params);
129 | }
130 |
131 | @override
132 | Future getVolume(int textureId) async {
133 | final Map reply =
134 | await _channel.invokeMapMethodWithTexture(
135 | 'getVolume', TextureMessage(textureId: textureId)) as Map;
136 | return reply['volume'] as double;
137 | }
138 |
139 | @override
140 | Future setVolume(int textureId, double volume) {
141 | final Map params = {"volume": volume};
142 | return _channel.invokeMapMethodWithTexture(
143 | 'setVolume', TextureMessage(textureId: textureId), params);
144 | }
145 |
146 | @override
147 | Future getVideoSize(int textureId) async {
148 | final Map reply =
149 | await _channel.invokeMapMethodWithTexture(
150 | 'getVideoSize', TextureMessage(textureId: textureId)) as Map;
151 | if (reply.containsKey("width") && reply.containsKey("height")) {
152 | return Size(reply["width"] as double, reply["height"] as double);
153 | }
154 | return null;
155 | }
156 |
157 | @override
158 | Future setPlaybackRate(int textureId, double speedRate) {
159 | final Map params = {
160 | "speedRate": speedRate
161 | };
162 | return _channel.invokeMapMethodWithTexture(
163 | 'setPlaybackSpeed', TextureMessage(textureId: textureId), params);
164 | }
165 |
166 | @override
167 | Future getPlaybackRate(int textureId) async {
168 | final Map reply =
169 | await _channel.invokeMapMethodWithTexture(
170 | 'getPlaybackSpeed', TextureMessage(textureId: textureId)) as Map;
171 | return reply['speedRate'] as double;
172 | }
173 |
174 | @override
175 | Future initialize(bool autoplay) async {
176 | final Map params = {"autoplay": autoplay};
177 | final Map? reply =
178 | await _channel.invokeMapMethod('initialize', params);
179 | int textureId = reply!['textureId']! as int;
180 | return textureId;
181 | }
182 |
183 | @override
184 | Future create(int textureId, VideoOptions videoOptions) {
185 | return setVideoOptions(textureId, videoOptions);
186 | }
187 |
188 | @override
189 | Future dispose(int textureId) {
190 | return _channel.invokeMapMethodWithTexture(
191 | 'dispose', TextureMessage(textureId: textureId));
192 | }
193 |
194 | @override
195 | Future play(int textureId) {
196 | return _channel.invokeMapMethodWithTexture(
197 | 'play', TextureMessage(textureId: textureId));
198 | }
199 |
200 | @override
201 | Future pause(int textureId) {
202 | return _channel.invokeMapMethodWithTexture(
203 | 'pause', TextureMessage(textureId: textureId));
204 | }
205 |
206 | @override
207 | Future seek(int textureId, int offset) {
208 | final Map params = {"offset": offset};
209 | return _channel.invokeMapMethodWithTexture(
210 | 'seek', TextureMessage(textureId: textureId), params);
211 | }
212 |
213 | @override
214 | Widget buildView(int textureId) {
215 | return Texture(textureId: textureId);
216 | }
217 |
218 | @override
219 | Stream playerEventsFor(int textureId) {
220 | return _eventChannelFor(textureId)
221 | .receiveBroadcastStream()
222 | .map((dynamic map) {
223 | final Map event = map as Map;
224 | switch (event['type']) {
225 | case 'ready':
226 | return PlayerEvent(type: PlayerEventType.ready);
227 | case 'played':
228 | return PlayerEvent(type: PlayerEventType.played);
229 | case 'paused':
230 | return PlayerEvent(type: PlayerEventType.paused);
231 | case 'seek':
232 | return PlayerEvent(type: PlayerEventType.seek);
233 | case 'seekStarted':
234 | return PlayerEvent(type: PlayerEventType.seekStarted);
235 | case 'ended':
236 | return PlayerEvent(type: PlayerEventType.ended);
237 | default:
238 | return PlayerEvent(type: PlayerEventType.unknown);
239 | }
240 | });
241 | }
242 |
243 | EventChannel _eventChannelFor(int textureId) {
244 | return EventChannel('video.api.player/events$textureId');
245 | }
246 | }
247 |
248 | /// Internal extensions on [MethodChannel] to handle [TextureMessage].
249 | extension MethodChannelExtension on MethodChannel {
250 | Future