返回,key为url/fileId
226 |
227 | Prop | Type | Note
228 | ---|---|---
229 | downloadStatus | String | "start"、"progress"、"stop"、"complete"、"error"
230 | quanlity | int | 1: "FLU"、2: "SD"、3: "HD"、4: "FHD"、5: "2K"、6: "4K"
231 | duration | int |
232 | size | int | 文件大小
233 | downloadSize | int | 已下载大小
234 | progress | int | 已下载进度
235 | playPath | String | 下载文件的绝对路径
236 | isStop | bool | 是否暂停下载
237 | url | String | 下载的视频链接
238 | fileId | String | 下载的视频FileId
239 | error | String | 下载的错误信息
240 |
241 |
242 |
243 | # 4.[Example](https://github.com/qq326646683/flutter_tencentplayer/blob/master/example/lib/main.dart)
244 |
245 | # 5.打包注意事项
246 | > 1. flutter打包命令: flutter build apk --release
247 |
248 | > 2. 混淆配置:
249 | ```
250 | 1. android/app/build.gradle
251 |
252 | buildTypes {
253 | release {
254 | ...
255 | // add
256 | ndk {
257 | abiFilters 'armeabi-v7a'
258 | }
259 | shrinkResources true
260 | minifyEnabled true
261 | useProguard true
262 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
263 | }
264 | }
265 |
266 | 2.android/app/proguard-rules.pro没有该文件则新建
267 | -keep class com.tencent.** { *; }
268 | ```
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'com.jinxian.flutter_tencentplayer'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 |
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.3.0'
12 | }
13 | }
14 |
15 | rootProject.allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | }
20 | }
21 |
22 | apply plugin: 'com.android.library'
23 |
24 | android {
25 | compileSdkVersion 28
26 |
27 | defaultConfig {
28 | minSdkVersion 16
29 |
30 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
31 | }
32 | lintOptions {
33 | disable 'InvalidPackage'
34 | }
35 |
36 | }
37 |
38 |
39 | dependencies {
40 | implementation fileTree(include: ['*.jar'], dir: 'libs')
41 | implementation(name: 'LiteAVSDK_Player_9.2.10639', ext: 'aar')
42 | implementation(name: 'libsuperplayer', ext: 'aar')
43 | // 超级播放器弹幕集成的第三方库
44 | implementation 'com.github.ctiao:DanmakuFlameMaster:0.5.3'
45 | androidTestImplementation 'androidx.test:runner:1.1.1'
46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableJetifier=true
3 | android.useAndroidX=true
4 |
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 02 21:43:42 CST 2019
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-4.10.2-all.zip
7 |
--------------------------------------------------------------------------------
/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/libs/LiteAVSDK_Player_9.2.10639.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/android/libs/LiteAVSDK_Player_9.2.10639.aar
--------------------------------------------------------------------------------
/android/libs/libsuperplayer.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/android/libs/libsuperplayer.aar
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'flutter_tencentplayer'
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/java/com/jinxian/flutter_tencentplayer/TencentQueuingEventSink.java:
--------------------------------------------------------------------------------
1 | package com.jinxian.flutter_tencentplayer;
2 |
3 | import java.util.ArrayList;
4 |
5 | import io.flutter.plugin.common.EventChannel;
6 |
7 | /**
8 | * And implementation of {@link EventChannel.EventSink} which can wrap an underlying sink.
9 | *
10 | * It delivers messages immediately when downstream is available, but it queues messages before
11 | * the delegate event sink is set with setDelegate.
12 | *
13 | *
This class is not thread-safe. All calls must be done on the same thread or synchronized
14 | * externally.
15 | */
16 | final class TencentQueuingEventSink implements EventChannel.EventSink {
17 | private EventChannel.EventSink delegate;
18 | private ArrayList eventQueue = new ArrayList<>();
19 | private boolean done = false;
20 |
21 | public void setDelegate(EventChannel.EventSink delegate) {
22 | this.delegate = delegate;
23 | maybeFlush();
24 | }
25 |
26 | @Override
27 | public void endOfStream() {
28 | enqueue(new EndOfStreamEvent());
29 | maybeFlush();
30 | done = true;
31 | }
32 |
33 | @Override
34 | public void error(String code, String message, Object details) {
35 | enqueue(new ErrorEvent(code, message, details));
36 | maybeFlush();
37 | }
38 |
39 | @Override
40 | public void success(Object event) {
41 | enqueue(event);
42 | maybeFlush();
43 | }
44 |
45 | private void enqueue(Object event) {
46 | if (done) {
47 | return;
48 | }
49 | eventQueue.add(event);
50 | }
51 |
52 | private void maybeFlush() {
53 | if (delegate == null) {
54 | return;
55 | }
56 | for (Object event : eventQueue) {
57 | if (event instanceof EndOfStreamEvent) {
58 | delegate.endOfStream();
59 | } else if (event instanceof ErrorEvent) {
60 | ErrorEvent errorEvent = (ErrorEvent) event;
61 | delegate.error(errorEvent.code, errorEvent.message, errorEvent.details);
62 | } else {
63 | delegate.success(event);
64 | }
65 | }
66 | eventQueue.clear();
67 | }
68 |
69 | private static class EndOfStreamEvent {}
70 |
71 | private static class ErrorEvent {
72 | String code;
73 | String message;
74 | Object details;
75 |
76 | ErrorEvent(String code, String message, Object details) {
77 | this.code = code;
78 | this.message = message;
79 | this.details = details;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/android/src/main/java/com/jinxian/flutter_tencentplayer/Util.java:
--------------------------------------------------------------------------------
1 | package com.jinxian.flutter_tencentplayer;
2 |
3 | import android.media.MediaMetadataRetriever;
4 |
5 | import java.lang.reflect.Field;
6 | import java.util.HashMap;
7 |
8 | public class Util {
9 | public static HashMap convertToMap(Object obj) {
10 |
11 | HashMap map = new HashMap();
12 | Field[] fields = obj.getClass().getDeclaredFields();
13 | for (int i = 0, len = fields.length; i < len; i++) {
14 | String varName = fields[i].getName();
15 | boolean accessFlag = fields[i].isAccessible();
16 | fields[i].setAccessible(true);
17 |
18 | Object o = null;
19 | try {
20 | o = fields[i].get(obj);
21 | } catch (IllegalAccessException e) {
22 | e.printStackTrace();
23 | }
24 | if (o != null)
25 | map.put(varName, o.toString());
26 |
27 | fields[i].setAccessible(accessFlag);
28 | }
29 |
30 | return map;
31 | }
32 |
33 | public static int getNetworkVideoRotate(String mUri) {
34 | int rotation = 0;
35 | MediaMetadataRetriever mmr = new MediaMetadataRetriever();
36 | try {
37 | if (mUri != null) {
38 | HashMap headers = null;
39 | if (headers == null) {
40 | headers = new HashMap();
41 | headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; zh-CN; MW-KW-001 Build/JRO03C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/1.0.0.001 U4/0.8.0 Mobile Safari/533.1");
42 | }
43 | if (mUri.startsWith("http")) {
44 | mmr.setDataSource(mUri, headers);
45 | } else {
46 | mmr.setDataSource(mUri);
47 | }
48 | }
49 |
50 | String rotationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); // 视频旋转方向
51 | if (rotationStr!=null && !rotationStr.isEmpty()) {
52 | rotation = Integer.parseInt(rotationStr);
53 | }
54 | } catch (Exception ex) {
55 | ex.printStackTrace();
56 | } finally {
57 | mmr.release();
58 | }
59 | return rotation;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/example/.flutter-plugins-dependencies:
--------------------------------------------------------------------------------
1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_forbidshot","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_forbidshot-0.0.2/","dependencies":[]},{"name":"flutter_tencentplayer","path":"/Users/ypp/nell_pro/flutter/flutter_tencentplayer/","dependencies":[]},{"name":"image_picker","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.7+7/","dependencies":[]},{"name":"path_provider","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.5.0/","dependencies":[]},{"name":"screen","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/screen-0.0.5/","dependencies":[]}],"android":[{"name":"flutter_forbidshot","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_forbidshot-0.0.2/","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_plugin_android_lifecycle-1.0.11/","dependencies":[]},{"name":"flutter_tencentplayer","path":"/Users/ypp/nell_pro/flutter/flutter_tencentplayer/","dependencies":[]},{"name":"image_picker","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.7+7/","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"path_provider","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.5.0/","dependencies":[]},{"name":"screen","path":"/Users/ypp/dev/flutter/.pub-cache/hosted/pub.flutter-io.cn/screen-0.0.5/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_forbidshot","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"flutter_tencentplayer","dependencies":[]},{"name":"image_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"path_provider","dependencies":[]},{"name":"screen","dependencies":[]}],"date_created":"2021-10-15 11:27:58.498145","version":"2.5.2"}
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | /build/
31 |
32 | # Android related
33 | **/android/**/gradle-wrapper.jar
34 | **/android/.gradle
35 | **/android/captures/
36 | **/android/gradlew
37 | **/android/gradlew.bat
38 | **/android/local.properties
39 | **/android/**/GeneratedPluginRegistrant.java
40 |
41 | # iOS/XCode related
42 | **/ios/**/*.mode1v3
43 | **/ios/**/*.mode2v3
44 | **/ios/**/*.moved-aside
45 | **/ios/**/*.pbxuser
46 | **/ios/**/*.perspectivev3
47 | **/ios/**/*sync/
48 | **/ios/**/.sconsign.dblite
49 | **/ios/**/.tags*
50 | **/ios/**/.vagrant/
51 | **/ios/**/DerivedData/
52 | **/ios/**/Icon?
53 | **/ios/**/Pods/
54 | **/ios/**/.symlinks/
55 | **/ios/**/profile
56 | **/ios/**/xcuserdata
57 | **/ios/.generated/
58 | **/ios/Flutter/App.framework
59 | **/ios/Flutter/Flutter.framework
60 | **/ios/Flutter/Generated.xcconfig
61 | **/ios/Flutter/app.flx
62 | **/ios/Flutter/app.zip
63 | **/ios/Flutter/flutter_assets/
64 | **/ios/ServiceDefinitions.json
65 | **/ios/Runner/GeneratedPluginRegistrant.*
66 |
67 | # Exceptions to above rules.
68 | !**/ios/**/default.mode1v3
69 | !**/ios/**/default.mode2v3
70 | !**/ios/**/default.pbxuser
71 | !**/ios/**/default.perspectivev3
72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
73 |
--------------------------------------------------------------------------------
/example/.gradle/4.10.2/fileChanges/last-build.bin:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/.gradle/4.10.2/fileHashes/fileHashes.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/.gradle/4.10.2/fileHashes/fileHashes.lock
--------------------------------------------------------------------------------
/example/.gradle/4.10.2/gc.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/.gradle/4.10.2/gc.properties
--------------------------------------------------------------------------------
/example/.gradle/4.10.2/taskHistory/taskHistory.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/.gradle/4.10.2/taskHistory/taskHistory.lock
--------------------------------------------------------------------------------
/example/.gradle/buildOutputCleanup/buildOutputCleanup.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/.gradle/buildOutputCleanup/buildOutputCleanup.lock
--------------------------------------------------------------------------------
/example/.gradle/buildOutputCleanup/cache.properties:
--------------------------------------------------------------------------------
1 | #Thu Aug 08 16:15:31 CST 2019
2 | gradle.version=4.10.2
3 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: b712a172f9694745f50505c93340883493b505e5
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_tencentplayer_example
2 |
3 | Demonstrates how to use the flutter_tencentplayer plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 28
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "com.jinxian.flutter_tencentplayer_example"
37 | minSdkVersion 16
38 | targetSdkVersion 28
39 | versionCode flutterVersionCode.toInteger()
40 | versionName flutterVersionName
41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
42 | }
43 |
44 | signingConfigs {
45 | release {
46 | storeFile file("sign.keystore") // 替换成你实际密匙文件所在位置
47 | storePassword "123456" // 替换成你实际的密码
48 | keyAlias "sign.keystore" // 替换
49 | keyPassword "123456" // 替换
50 | }
51 | }
52 |
53 | buildTypes {
54 | release {
55 | signingConfig signingConfigs.release
56 | ndk {
57 | abiFilters 'armeabi-v7a'
58 | }
59 | shrinkResources true
60 | minifyEnabled true
61 | useProguard true
62 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
63 | }
64 | debug {
65 | signingConfig signingConfigs.release
66 | }
67 | }
68 | }
69 |
70 | flutter {
71 | source '../..'
72 | }
73 |
74 | dependencies {
75 | testImplementation 'junit:junit:4.12'
76 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
77 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
78 | }
79 |
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class com.tencent.** { *; }
--------------------------------------------------------------------------------
/example/android/app/sign.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/android/app/sign.keystore
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
40 |
44 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/jinxian/flutter_tencentplayer_example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jinxian.flutter_tencentplayer_example;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.app.FlutterActivity;
5 | import io.flutter.plugins.GeneratedPluginRegistrant;
6 |
7 | public class MainActivity extends FlutterActivity {
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | GeneratedPluginRegistrant.registerWith(this);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/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/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.2.1'
9 | }
10 | }
11 |
12 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
13 | def plugins = new Properties()
14 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
15 | if (pluginsFile.exists()) {
16 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | flatDir {
24 | dirs "${plugins.get("flutter_tencentplayer")}android/libs"
25 | }
26 | maven { url 'http://download.flutter.io' }
27 | }
28 | }
29 |
30 | rootProject.buildDir = '../build'
31 | subprojects {
32 | project.buildDir = "${rootProject.buildDir}/${project.name}"
33 | }
34 | subprojects {
35 | project.evaluationDependsOn(':app')
36 | }
37 |
38 | task clean(type: Delete) {
39 | delete rootProject.buildDir
40 | }
41 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
3 | android.enableR8=true
4 |
--------------------------------------------------------------------------------
/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-4.10.2-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.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, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/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/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/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/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/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 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | flutter_tencentplayer_example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | NSAppTransportSecurity
26 |
27 | NSAllowsArbitraryLoads
28 |
29 |
30 | NSPhotoLibraryUsageDescription
31 | 相册
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UIMainStoryboardFile
35 | Main
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UIViewControllerBasedStatusBarAppearance
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/example/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 |
5 | import 'page/index.dart';
6 |
7 | void main() {
8 | WidgetsFlutterBinding.ensureInitialized();
9 | SystemChrome.setPreferredOrientations([
10 | DeviceOrientation.portraitUp,
11 | ]);
12 | runApp(MyApp());
13 | }
14 |
15 | class MyApp extends StatefulWidget {
16 | @override
17 | _MyAppState createState() => _MyAppState();
18 | }
19 |
20 | class _MyAppState extends State {
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return MaterialApp(
25 | theme: ThemeData(primaryColor: Colors.pink),
26 | debugShowCheckedModeBanner: false,
27 | routes: {
28 | '/': (_) => ListPage(),
29 | 'window': (_) => WindowVideoPage(),
30 | 'full': (_) => FullVideoPage(),
31 | 'download': (_) => DownloadPage(),
32 | }
33 | );
34 | }
35 | }
36 |
37 | enum PlayType {
38 | network,
39 | asset,
40 | file,
41 | fileId,
42 | }
43 |
44 | String networkMp4 = 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4';
45 | String liveUrl1 = 'http://liteavapp.qcloud.com/live/liteavdemoplayerstreamid_demo1080p.flv';
46 | String liveUrl2 = 'rtmp://58.200.131.2:1935/livetv/hunantv';
47 | String assetPath = 'static/tencent1.mp4';
48 | String rotateNetworkMp4 = 'http://file.jinxianyun.com/tencentplayer_rotate.mp4';
49 | String invalidMp4 = 'https://123456';
50 |
51 |
52 | class ListPage extends StatelessWidget {
53 | @override
54 | Widget build(BuildContext context) {
55 | return Scaffold(
56 | appBar: AppBar(title: Text('腾讯播放器Demo'),),
57 | body: ListView(
58 | children: [
59 | ListTile(
60 | title: Text('小窗视频'),
61 | onTap: () {
62 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => WindowVideoPage(playType: PlayType.network, dataSource: networkMp4,)));
63 | },
64 | ),
65 | ListTile(
66 | title: Text('网络视频'),
67 | onTap: () {
68 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.network, dataSource: networkMp4,)));
69 | },
70 | ),
71 | ListTile(
72 | title: Text('直播1'),
73 | onTap: () {
74 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.network, dataSource: liveUrl1, showBottomWidget: false, showClearBtn: false,)));
75 | },
76 | ),
77 | ListTile(
78 | title: Text('直播2'),
79 | onTap: () {
80 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.network, dataSource: liveUrl2, showBottomWidget: false, showClearBtn: false,)));
81 | },
82 | ),
83 | ListTile(
84 | title: Text('file视频'),
85 | onTap: () {
86 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FileVideoPage()));
87 | },
88 | ),
89 | ListTile(
90 | title: Text('asset视频'),
91 | onTap: () {
92 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.asset, dataSource: assetPath,)));
93 | },
94 | ),
95 | ListTile(
96 | title: Text('下载播放视频'),
97 | onTap: () {
98 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => DownloadPage()));
99 | },
100 | ),
101 | ListTile(
102 | title: Text('自动切换集'),
103 | onTap: () {
104 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => AutoChangeNextSourcePage()));
105 | },
106 | ),
107 | ListTile(
108 | title: Text('仿抖音'),
109 | onTap: () {
110 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => TiktokPage()));
111 | },
112 | ),
113 | ListTile(
114 | title: Text('带旋转属性的视频'),
115 | onTap: () {
116 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.network, dataSource: rotateNetworkMp4,)));
117 | },
118 | ),
119 | ListTile(
120 | title: Text('播放无效链接'),
121 | onTap: () {
122 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.network, dataSource: invalidMp4,)));
123 | },
124 | ),
125 | ],
126 | ),
127 | );
128 | }
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/example/lib/page/auto_change_next_source_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_tencentplayer_example/util/common_util.dart';
5 | import 'package:screen/screen.dart';
6 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
7 |
8 | class AutoChangeNextSourcePage extends StatefulWidget {
9 | @override
10 | _AutoChangeNextSourcePageState createState() =>
11 | _AutoChangeNextSourcePageState();
12 | }
13 |
14 | class _AutoChangeNextSourcePageState extends State {
15 | TencentPlayerController? controller;
16 | VoidCallback? listener;
17 | int currentIndex = 0;
18 |
19 | List urlList = [
20 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4',
21 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f20.mp4',
22 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f30.mp4',
23 | ];
24 |
25 | _AutoChangeNextSourcePageState() {
26 | listener = () {
27 | if (!mounted) {
28 | return;
29 | }
30 | if (controller!.value.duration != Duration() && controller!.value.position == controller!.value.duration) {
31 | CommonUtils.throttle(_next, durationTime: 3000);
32 | }
33 | setState(() {});
34 | };
35 | }
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 | controller = TencentPlayerController.network(urlList[0]);
41 | controller!.initialize();
42 | controller!.addListener(listener!);
43 | Screen.keepOn(true);
44 | }
45 |
46 | @override
47 | void dispose() {
48 | super.dispose();
49 | controller!.removeListener(listener!);
50 | controller!.dispose();
51 | Screen.keepOn(false);
52 | }
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return Scaffold(
57 | body: Column(
58 | children: [
59 | /// 视频
60 | controller!.value.initialized
61 | ? AspectRatio(
62 | aspectRatio: controller!.value.aspectRatio,
63 | child: TencentPlayer(controller!),
64 | )
65 | : Image.asset('static/place_nodata.png'),
66 | Text('${currentIndex + 1}集')
67 | ],
68 | ),
69 | );
70 | }
71 |
72 | _next() {
73 | currentIndex++;
74 | controller?.removeListener(listener!);
75 | controller?.pause();
76 | controller = TencentPlayerController.network(urlList[currentIndex % 3]);
77 | controller?.initialize();
78 | controller?.addListener(listener!);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/example/lib/page/download_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_tencentplayer_example/page/index.dart';
6 | import 'package:path_provider/path_provider.dart';
7 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
8 | import 'package:flutter_tencentplayer_example/main.dart';
9 |
10 |
11 | class DownloadPage extends StatefulWidget {
12 | @override
13 | _DownloadPageState createState() => _DownloadPageState();
14 | }
15 |
16 | class _DownloadPageState extends State {
17 | DownloadController? _downloadController;
18 | VoidCallback? downloadListener;
19 |
20 | List urlList= [
21 | "http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8",
22 | "http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/68e3febf4564972819220421305/v.f220.m3u8",
23 | ];
24 |
25 | _DownloadPageState() {
26 | downloadListener = () {
27 | if (!mounted) {
28 | return;
29 | }
30 | setState(() {});
31 | };
32 | }
33 |
34 | @override
35 | void initState() {
36 | super.initState();
37 | _init();
38 | }
39 |
40 | _init() async {
41 | Directory appDocDir = await getApplicationDocumentsDirectory();
42 | String appDocPath = appDocDir.path;
43 | _downloadController = DownloadController(appDocPath);
44 | _downloadController!.addListener(downloadListener!);
45 | setState(() {
46 | });
47 | }
48 |
49 | @override
50 | void dispose() {
51 | super.dispose();
52 | _downloadController!.removeListener(downloadListener!);
53 | }
54 |
55 | @override
56 | Widget build(BuildContext context) {
57 | return Scaffold(
58 | appBar: AppBar(title: Text('下载播放视频'),),
59 | body: Column(
60 | children: [
61 | Text('(只支持腾讯的fileId/m3u8视频下载)'),
62 | Text('请先自行设置文件读写权限', style: TextStyle(color: Colors.red),),
63 | getItem(0),
64 | getItem(1),
65 | ],
66 | ),
67 | );
68 | }
69 |
70 | Widget getItem(int index) {
71 | if (_downloadController == null) {
72 | return SizedBox();
73 | }
74 | DownloadValue? value = _downloadController!.value[urlList[index]];
75 | return Card(
76 | child: Padding(
77 | padding: const EdgeInsets.all(20.0),
78 | child: Row(
79 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
80 | children: [
81 | Row(
82 | children: [
83 | Row(
84 | children: [
85 | Text(getStatusTxt(value?.downloadStatus ?? '')),
86 | SizedBox(width: 10,),
87 | value != null ? Text('${((value.downloadSize)!/1024/1024).toStringAsFixed(2)}M/${((value.size)!/1024/1024).toStringAsFixed(2)}M') : SizedBox(),
88 | ],
89 | ),
90 | SizedBox(width: 10,),
91 | value?.playPath != null ? GestureDetector(
92 | onTap: () {
93 | Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(playType: PlayType.file, dataSource: value?.playPath, showClearBtn: false,)));
94 | },
95 | child: Container(
96 | width: 30,
97 | height: 30,
98 | decoration: BoxDecoration(
99 | border: Border.all(width: 1, color: Color(0xfffe373c)),
100 | borderRadius: BorderRadius.all(Radius.circular(15))
101 | ),
102 | child: Image.asset('static/player_play.png', color: Color(0xfffe373c),),
103 | ),
104 | ): SizedBox(),
105 | ],
106 | ),
107 | GestureDetector(
108 | onTap: () {
109 | if (value?.downloadStatus == 'progress') {
110 | _downloadController!.pauseDownload(urlList[index]);
111 | } else {
112 | _downloadController!.dowload(urlList[index]);
113 | }
114 | },
115 | child: Text(getBtnTxt(value?.downloadStatus ?? ''), style: TextStyle(color: Colors.blue),),
116 | ),
117 | ],
118 | ),
119 | ),
120 | );
121 | }
122 |
123 | String getStatusTxt(String downloadStatus) {
124 | String tip = '';
125 | switch (downloadStatus) {
126 | case 'start':
127 | tip = '开始下载中';
128 | break;
129 | case 'progress':
130 | tip = '下载中';
131 | break;
132 | case 'stop':
133 | tip = '已暂停';
134 | break;
135 | case 'complete':
136 | tip = '已完成';
137 | break;
138 | case 'error':
139 | tip = '下载出错';
140 | }
141 | return tip;
142 | }
143 |
144 | String getBtnTxt(String downloadStatus) {
145 | String tip = '开始下载';
146 | switch (downloadStatus) {
147 | case 'progress':
148 | tip = '暂停';
149 | break;
150 | }
151 | return tip;
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/example/lib/page/file_video_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
4 | import 'package:flutter_tencentplayer_example/main.dart';
5 | import 'package:flutter_tencentplayer_example/page/index.dart';
6 | import 'package:image_picker/image_picker.dart';
7 |
8 | class FileVideoPage extends StatefulWidget {
9 | @override
10 | _FileVideoPageState createState() => _FileVideoPageState();
11 | }
12 |
13 | class _FileVideoPageState extends State {
14 | ImagePicker picker = ImagePicker();
15 | TencentPlayerController? controller;
16 | VoidCallback? listener;
17 |
18 | _FileVideoPageState() {
19 | listener = () {
20 | if (!mounted) {
21 | return;
22 | }
23 | setState(() {});
24 | };
25 | }
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return Scaffold(
30 | appBar: AppBar(
31 | title: Text('播放本地file${controller?.value?.degree}'),
32 | ),
33 | body: controller?.value?.initialized == true
34 | ? GestureDetector(
35 | onTap: () {
36 | if (controller!.value.isPlaying) {
37 | controller!.pause();
38 | } else {
39 | controller!.play();
40 | }
41 | },
42 | child: Stack(
43 | fit: StackFit.expand,
44 | alignment: Alignment.center,
45 | children: [
46 | AspectRatio(
47 | aspectRatio: controller!.value.aspectRatio,
48 | child: TencentPlayer(controller!),
49 | ),
50 | !controller!.value.isPlaying
51 | ? Icon(
52 | Icons.play_arrow,
53 | size: 100,
54 | color: Colors.white70,
55 | )
56 | : SizedBox(),
57 | ],
58 | ),
59 | )
60 | : Image.asset('static/place_nodata.png'),
61 | floatingActionButton: FloatingActionButton(
62 | onPressed: _select,
63 | backgroundColor: Colors.pink,
64 | child: Icon(Icons.photo_size_select_actual),
65 | ),
66 | );
67 | }
68 |
69 | _select() async {
70 | PickedFile file = await picker.getVideo(source: ImageSource.gallery);
71 | print(file.path);
72 | initPlayer(file.path);
73 | }
74 |
75 | void initPlayer(String path) {
76 | controller = TencentPlayerController.file(path)
77 | ..initialize()
78 | ..addListener(listener!);
79 | }
80 |
81 | @override
82 | void dispose() {
83 | controller?.removeListener(listener!);
84 | controller?.dispose();
85 | super.dispose();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/example/lib/page/full_video_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_tencentplayer_example/widget/tencent_player_bottom_widget.dart';
6 | import 'package:flutter_tencentplayer_example/widget/tencent_player_gesture_cover.dart';
7 | import 'package:flutter_tencentplayer_example/widget/tencent_player_loading.dart';
8 | import 'package:screen/screen.dart';
9 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
10 | import 'package:flutter_tencentplayer_example/main.dart';
11 | import 'package:flutter_tencentplayer_example/util/forbidshot_util.dart';
12 |
13 | class FullVideoPage extends StatefulWidget {
14 | PlayType playType;
15 | String? dataSource;
16 | TencentPlayerController? controller;
17 |
18 | //UI
19 | bool showBottomWidget;
20 | bool showClearBtn;
21 |
22 | FullVideoPage(
23 | {this.controller,
24 | this.showBottomWidget = true,
25 | this.showClearBtn = true,
26 | this.dataSource,
27 | this.playType = PlayType.network});
28 |
29 | @override
30 | _FullVideoPageState createState() => _FullVideoPageState();
31 | }
32 |
33 | class _FullVideoPageState extends State {
34 | TencentPlayerController? controller;
35 | VoidCallback? listener;
36 |
37 | bool isLock = false;
38 | bool showCover = false;
39 | Timer? timer;
40 |
41 | _FullVideoPageState() {
42 | listener = () {
43 | if (!mounted) {
44 | return;
45 | }
46 | setState(() {});
47 | };
48 | }
49 |
50 | @override
51 | void initState() {
52 | super.initState();
53 | SystemChrome.setEnabledSystemUIOverlays([]);
54 | SystemChrome.setPreferredOrientations([
55 | DeviceOrientation.landscapeLeft,
56 | DeviceOrientation.landscapeRight,
57 | ]);
58 | _initController();
59 | controller!.addListener(listener!);
60 | hideCover();
61 | ForbidShotUtil.initForbid(context);
62 | Screen.keepOn(true);
63 | }
64 |
65 | @override
66 | void dispose() {
67 | super.dispose();
68 | SystemChrome.setEnabledSystemUIOverlays(
69 | [SystemUiOverlay.top, SystemUiOverlay.bottom]);
70 | SystemChrome.setPreferredOrientations([
71 | DeviceOrientation.portraitUp,
72 | ]);
73 | controller!.removeListener(listener!);
74 | if (widget.controller == null) {
75 | controller!.dispose();
76 | }
77 | ForbidShotUtil.disposeForbid();
78 | Screen.keepOn(false);
79 | }
80 |
81 | _initController() {
82 | if (widget.controller != null) {
83 | controller = widget.controller;
84 | return;
85 | }
86 | switch (widget.playType) {
87 | case PlayType.network:
88 | controller = TencentPlayerController.network(widget.dataSource);
89 | break;
90 | case PlayType.asset:
91 | controller = TencentPlayerController.asset(widget.dataSource);
92 | break;
93 | case PlayType.file:
94 | controller = TencentPlayerController.file(widget.dataSource!);
95 | break;
96 | case PlayType.fileId:
97 | controller = TencentPlayerController.network(null,
98 | playerConfig: PlayerConfig(
99 | auth: {"appId": 1252463788, "fileId": widget.dataSource}));
100 | break;
101 | }
102 | controller!.initialize();
103 | }
104 |
105 | @override
106 | Widget build(BuildContext context) {
107 | return Scaffold(
108 | body: GestureDetector(
109 | behavior: HitTestBehavior.opaque,
110 | onTap: () {
111 | hideCover();
112 | },
113 | onDoubleTap: () {
114 | if (!widget.showBottomWidget || isLock) return;
115 | if (controller!.value.isPlaying) {
116 | controller!.pause();
117 | } else {
118 | controller!.play();
119 | }
120 | },
121 | child: Container(
122 | color: Colors.black,
123 | child: Stack(
124 | overflow: Overflow.visible,
125 | alignment: Alignment.center,
126 | children: [
127 | /// 视频
128 | controller!.value.initialized
129 | ? AspectRatio(
130 | aspectRatio: controller!.value.aspectRatio,
131 | child: TencentPlayer(controller!),
132 | )
133 | : Image.asset('static/place_nodata.png'),
134 |
135 | /// 支撑全屏
136 | Container(),
137 |
138 | /// 半透明浮层
139 | showCover ? Container(color: Color(0x7f000000)) : SizedBox(),
140 |
141 | /// 处理滑动手势
142 | Offstage(
143 | offstage: isLock,
144 | child: TencentPlayerGestureCover(
145 | controller: controller!,
146 | showBottomWidget: widget.showBottomWidget,
147 | behavingCallBack: delayHideCover,
148 | ),
149 | ),
150 |
151 | /// 加载loading
152 | TencentPlayerLoading(
153 | controller: controller,
154 | iconW: 53,
155 | ),
156 |
157 | /// 头部浮层
158 | !isLock && showCover
159 | ? Positioned(
160 | top: 0,
161 | left: MediaQuery.of(context).padding.top,
162 | child: GestureDetector(
163 | behavior: HitTestBehavior.opaque,
164 | onTap: () {
165 | Navigator.pop(context, controller);
166 | },
167 | child: Container(
168 | padding: EdgeInsets.only(top: 34, left: 10),
169 | child: Image.asset(
170 | 'static/icon_back.png',
171 | width: 20,
172 | height: 20,
173 | fit: BoxFit.contain,
174 | color: Colors.white,
175 | ),
176 | ),
177 | ),
178 | )
179 | : SizedBox(),
180 |
181 | /// 锁
182 | showCover
183 | ? Align(
184 | alignment: Alignment.centerLeft,
185 | child: GestureDetector(
186 | behavior: HitTestBehavior.opaque,
187 | onTap: () {
188 | setState(() {
189 | isLock = !isLock;
190 | });
191 | delayHideCover();
192 | if (Platform.isAndroid) {
193 | DeviceOrientation deviceOrientation =
194 | controller!.value.orientation < 180
195 | ? DeviceOrientation.landscapeRight
196 | : DeviceOrientation.landscapeLeft;
197 | if (isLock) {
198 | SystemChrome.setPreferredOrientations(
199 | [deviceOrientation]);
200 | } else {
201 | SystemChrome.setPreferredOrientations([
202 | DeviceOrientation.landscapeLeft,
203 | DeviceOrientation.landscapeRight,
204 | ]);
205 | }
206 | }
207 | },
208 | child: Container(
209 | color: Colors.transparent,
210 | padding: EdgeInsets.only(
211 | top: MediaQuery.of(context).padding.top,
212 | right: 20,
213 | bottom: 20,
214 | left: MediaQuery.of(context).padding.top,
215 | ),
216 | child: Image.asset(
217 | isLock
218 | ? 'static/player_lock.png'
219 | : 'static/player_unlock.png',
220 | width: 38,
221 | height: 38,
222 | ),
223 | ),
224 | ),
225 | )
226 | : SizedBox(),
227 |
228 | /// 进度、清晰度、速度
229 | Offstage(
230 | offstage: !widget.showBottomWidget,
231 | child: Padding(
232 | padding: EdgeInsets.only(
233 | left: MediaQuery.of(context).padding.top,
234 | right: MediaQuery.of(context).padding.bottom),
235 | child: TencentPlayerBottomWidget(
236 | isShow: !isLock && showCover,
237 | controller: controller,
238 | showClearBtn: widget.showClearBtn,
239 | behavingCallBack: () {
240 | delayHideCover();
241 | },
242 | changeClear: (int index) {
243 | changeClear(index);
244 | },
245 | ),
246 | ),
247 | ),
248 | ],
249 | ),
250 | ),
251 | ),
252 | );
253 | }
254 |
255 | List clearUrlList = [
256 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4',
257 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f20.mp4',
258 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f30.mp4',
259 | ];
260 |
261 | changeClear(int urlIndex, {int? startTime}) {
262 | controller?.removeListener(listener!);
263 | controller?.pause();
264 | controller = TencentPlayerController.network(clearUrlList[urlIndex],
265 | playerConfig: PlayerConfig(
266 | startTime: startTime ?? controller!.value.position.inSeconds));
267 | controller?.initialize().then((_) {
268 | if (mounted) setState(() {});
269 | });
270 | controller?.addListener(listener!);
271 | }
272 |
273 | hideCover() {
274 | if (!mounted) return;
275 | setState(() {
276 | showCover = !showCover;
277 | });
278 | delayHideCover();
279 | }
280 |
281 | delayHideCover() {
282 | if (timer != null) {
283 | timer?.cancel();
284 | timer = null;
285 | }
286 | if (showCover) {
287 | timer = new Timer(Duration(seconds: 6), () {
288 | if (!mounted) return;
289 | setState(() {
290 | showCover = false;
291 | });
292 | });
293 | }
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/example/lib/page/index.dart:
--------------------------------------------------------------------------------
1 | export 'tiktok/tiktok_page.dart';
2 | export 'auto_change_next_source_page.dart';
3 | export 'download_page.dart';
4 | export 'full_video_page.dart';
5 | export 'window_video_page.dart';
6 | export 'file_video_page.dart';
--------------------------------------------------------------------------------
/example/lib/page/tiktok/item/home_tab_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
3 |
4 | class HomeTabItem extends StatefulWidget {
5 | final int index;
6 | final String url;
7 | final bool? isFocus;
8 |
9 | HomeTabItem(this.index, this.url, {this.isFocus});
10 |
11 | @override
12 | _HomeTabItemState createState() => _HomeTabItemState();
13 | }
14 |
15 | class _HomeTabItemState extends State with AutomaticKeepAliveClientMixin{
16 | String fileBase = 'http://file.jinxianyun.com/';
17 | TencentPlayerController? controller;
18 | VoidCallback? listener;
19 |
20 | _HomeTabItemState() {
21 | listener = () {
22 | if (!mounted) {
23 | return;
24 | }
25 | setState(() {});
26 | };
27 | }
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | print('initState:${widget.index}:${widget.url}');
33 | if (widget.index == 0) {
34 | controller = TencentPlayerController.network('$fileBase${widget.url}', playerConfig: PlayerConfig(loop: true))
35 | ..initialize()
36 | ..addListener(listener!);
37 | }
38 | }
39 |
40 | @override
41 | void didUpdateWidget(HomeTabItem oldWidget) {
42 | super.didUpdateWidget(oldWidget);
43 | if (oldWidget.isFocus == false && widget.isFocus == true) {
44 | print('获得焦点didUpdateWidget:${widget.index}:${widget.url}');
45 | controller = TencentPlayerController.network('$fileBase${widget.url}', playerConfig: PlayerConfig(loop: true))
46 | ..initialize()
47 | ..addListener(listener!);
48 | } else {
49 | print('失去焦点didUpdateWidget:${widget.index}:${widget.url}');
50 | controller?.removeListener(listener!);
51 | controller?.pause();
52 | }
53 | }
54 |
55 | @override
56 | void dispose() {
57 | print('dispose:${widget.index}:${widget.url}');
58 | controller?.removeListener(listener!);
59 | controller?.dispose();
60 | super.dispose();
61 |
62 | }
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | if (controller == null) {
67 | return Image.asset('static/place_nodata.png');
68 | }
69 | return controller!.value.initialized
70 | ? GestureDetector(
71 | onTap: () {
72 | if (controller!.value.isPlaying) {
73 | controller!.pause();
74 | } else {
75 | controller!.play();
76 | }
77 | },
78 | child: Stack(
79 | fit: StackFit.expand,
80 | alignment: Alignment.center,
81 | children: [
82 | AspectRatio(
83 | aspectRatio: controller!.value.aspectRatio,
84 | child: TencentPlayer(controller!),
85 | ),
86 | !controller!.value.isPlaying ? Icon(Icons.play_arrow, size: 100, color: Colors.white70,): SizedBox(),
87 | ],
88 | ),
89 | )
90 | : Image.asset('static/place_nodata.png');
91 | }
92 |
93 | @override
94 | bool get wantKeepAlive => true;
95 | }
96 |
--------------------------------------------------------------------------------
/example/lib/page/tiktok/tab/home_tab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_tencentplayer_example/page/tiktok/item/home_tab_item.dart';
3 |
4 | class HomeTab extends StatefulWidget {
5 | @override
6 | _HomeTabState createState() => _HomeTabState();
7 | }
8 |
9 | class _HomeTabState extends State with AutomaticKeepAliveClientMixin{
10 | List data = [
11 | 'F95C40DA159FFA548D9DB220D9028500.mp4',
12 | 'D2574481259D9883A571EA05FF354ACB.mp4',
13 | '27654ADA709264F6C12C9D2D6CE43864.mp4',
14 | '6438BF272694486859D5DE899DD2D823.mp4',
15 | ];
16 |
17 | int focusIndex = 0;
18 |
19 |
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | super.build(context);
24 | return PageView.builder(
25 | scrollDirection: Axis.vertical,
26 | itemBuilder: (_, index) => HomeTabItem(index, data[index], isFocus: focusIndex == index),
27 | itemCount: data.length,
28 | onPageChanged: (int index) {
29 | setState(() {
30 | focusIndex = index;
31 | });
32 | },
33 | );
34 | }
35 |
36 |
37 |
38 | @override
39 | bool get wantKeepAlive => true;
40 | }
41 |
--------------------------------------------------------------------------------
/example/lib/page/tiktok/tab/mine_tab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class MineTab extends StatefulWidget {
4 | @override
5 | _MineTabState createState() => _MineTabState();
6 | }
7 |
8 | class _MineTabState extends State with AutomaticKeepAliveClientMixin{
9 | @override
10 | Widget build(BuildContext context) {
11 | super.build(context);
12 | return Center(
13 | child: Text('mine'),
14 | );
15 | }
16 |
17 | @override
18 | bool get wantKeepAlive => true;
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/example/lib/page/tiktok/tiktok_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_tencentplayer_example/page/tiktok/tab/home_tab.dart';
3 | import 'package:flutter_tencentplayer_example/page/tiktok/tab/mine_tab.dart';
4 |
5 | class TiktokPage extends StatefulWidget {
6 | @override
7 | _TiktokPageState createState() => _TiktokPageState();
8 | }
9 |
10 | class _TiktokPageState extends State with TickerProviderStateMixin{
11 | TabController? tabController;
12 | @override
13 | void initState() {
14 | super.initState();
15 | tabController = TabController(length: 2, vsync: this);
16 | }
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | body: Column(
22 | children: [
23 | Expanded(
24 | child: TabBarView(
25 | controller: tabController,
26 | children: [
27 | HomeTab(),
28 | MineTab(),
29 | ],
30 | ),
31 | ),
32 | TabBar(
33 | controller: tabController,
34 | labelColor: Colors.pink,
35 | unselectedLabelColor: Colors.black,
36 | tabs: [
37 | Tab(text: 'Home',),
38 | Tab(text: 'Mine',),
39 | ],
40 | )
41 | ],
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example/lib/page/window_video_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_tencentplayer_example/page/index.dart';
6 | import 'package:flutter_tencentplayer_example/widget/tencent_player_bottom_widget.dart';
7 | import 'package:flutter_tencentplayer_example/widget/tencent_player_gesture_cover.dart';
8 | import 'package:flutter_tencentplayer_example/widget/tencent_player_loading.dart';
9 | import 'package:screen/screen.dart';
10 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
11 | import 'package:flutter_tencentplayer_example/main.dart';
12 | import 'package:flutter_tencentplayer_example/util/forbidshot_util.dart';
13 |
14 |
15 | class WindowVideoPage extends StatefulWidget {
16 | PlayType playType;
17 | String? dataSource;
18 |
19 | //UI
20 | bool showBottomWidget;
21 | bool showClearBtn;
22 |
23 |
24 | WindowVideoPage({this.showBottomWidget = true, this.showClearBtn = true, this.dataSource, this.playType = PlayType.network});
25 |
26 | @override
27 | _WindowVideoPageState createState() => _WindowVideoPageState();
28 | }
29 |
30 | class _WindowVideoPageState extends State {
31 | late TencentPlayerController? controller;
32 | late VoidCallback? listener;
33 | DeviceOrientation? deviceOrientation;
34 |
35 |
36 | bool isLock = false;
37 | bool showCover = false;
38 | Timer? timer;
39 |
40 |
41 | _WindowVideoPageState() {
42 | listener = () {
43 | if (!mounted) {
44 | return;
45 | }
46 | setState(() {});
47 | };
48 |
49 | }
50 |
51 | @override
52 | void initState() {
53 | super.initState();
54 | SystemChrome.setEnabledSystemUIOverlays([]);
55 | _initController();
56 | controller!.initialize();
57 | controller!.addListener(listener!);
58 | hideCover();
59 | ForbidShotUtil.initForbid(context);
60 | Screen.keepOn(true);
61 | }
62 |
63 |
64 | @override
65 | void dispose() {
66 | super.dispose();
67 | SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]);
68 | controller!.removeListener(listener!);
69 | controller!.dispose();
70 | ForbidShotUtil.disposeForbid();
71 | Screen.keepOn(false);
72 | }
73 |
74 | _initController() {
75 | switch (widget.playType) {
76 | case PlayType.network:
77 | controller = TencentPlayerController.network(widget.dataSource!);
78 | break;
79 | case PlayType.asset:
80 | controller = TencentPlayerController.asset(widget.dataSource!);
81 | break;
82 | case PlayType.file:
83 | controller = TencentPlayerController.file(widget.dataSource!);
84 | break;
85 | case PlayType.fileId:
86 | controller = TencentPlayerController.network(null, playerConfig: PlayerConfig(auth: {"appId": 1252463788, "fileId": widget.dataSource}));
87 | break;
88 | }
89 | }
90 |
91 |
92 | @override
93 | Widget build(BuildContext context) {
94 | return Scaffold(
95 | body: GestureDetector(
96 | behavior: HitTestBehavior.opaque,
97 | onTap: () {
98 | hideCover();
99 | },
100 | onDoubleTap: () {
101 | if (!widget.showBottomWidget || isLock) return;
102 | if (controller!.value.isPlaying) {
103 | controller!.pause();
104 | } else {
105 | controller!.play();
106 | }
107 | },
108 | child:Container(
109 | color: Colors.black,
110 | height: 240,
111 | child: Stack(
112 | overflow: Overflow.visible,
113 | alignment: Alignment.center,
114 | children: [
115 | /// 视频
116 | controller!.value.initialized
117 | ? AspectRatio(
118 | aspectRatio: controller!.value.aspectRatio,
119 | child: TencentPlayer(controller!),
120 | ) : Image.asset('static/place_nodata.png'),
121 | /// 支撑全屏
122 | Container(),
123 | /// 半透明浮层
124 | showCover ?Container(color: Color(0x7f000000)) : SizedBox(),
125 | /// 处理滑动手势
126 | Offstage(
127 | offstage: isLock,
128 | child: TencentPlayerGestureCover(
129 | controller: controller!,
130 | showBottomWidget: widget.showBottomWidget,
131 | behavingCallBack: delayHideCover,
132 | ),
133 | ),
134 | /// 加载loading
135 | TencentPlayerLoading(controller: controller, iconW: 53,),
136 | /// 头部浮层
137 | !isLock && showCover ? Positioned(
138 | top: 0,
139 | left: MediaQuery.of(context).padding.top,
140 | child: GestureDetector(
141 | behavior: HitTestBehavior.opaque,
142 | onTap: () {
143 | Navigator.pop(context);
144 | },
145 | child: Container(
146 | padding: EdgeInsets.only(top: 34, left: 10),
147 | child: Image.asset('static/icon_back.png', width: 20, height: 20, fit: BoxFit.contain, color: Colors.white,
148 | ),
149 | ),
150 | ),
151 | ) : SizedBox(),
152 | /// 锁
153 | showCover ? Align(
154 | alignment: Alignment.centerLeft,
155 | child: GestureDetector(
156 | behavior: HitTestBehavior.opaque,
157 | onTap: () {
158 | setState(() {
159 | isLock = !isLock;
160 | });
161 | delayHideCover();
162 | },
163 | child: Container(
164 | color: Colors.transparent,
165 | padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top, right: 20, bottom: 20, left: MediaQuery.of(context).padding.top,),
166 | child: Image.asset(isLock ? 'static/player_lock.png' : 'static/player_unlock.png', width: 38, height: 38,),
167 | ),
168 | ),
169 | ): SizedBox(),
170 | /// 进度、清晰度、速度
171 | Offstage(
172 | offstage: !widget.showBottomWidget,
173 | child: Padding(
174 | padding: EdgeInsets.only(left: MediaQuery.of(context).padding.top, right: MediaQuery.of(context).padding.bottom),
175 | child: TencentPlayerBottomWidget(
176 | isShow: !isLock && showCover,
177 | controller: controller,
178 | showClearBtn: widget.showClearBtn,
179 | behavingCallBack: () {
180 | delayHideCover();
181 | },
182 | changeClear: (int index) {
183 | changeClear(index);
184 | },
185 | ),
186 | ),
187 | ),
188 | /// 全屏按钮
189 | showCover ? Positioned(
190 | right: 0,
191 | bottom: 20,
192 | child: GestureDetector(
193 | behavior: HitTestBehavior.opaque,
194 | onTap: () async {
195 | TencentPlayerController newController = await Navigator.of(context).push(CupertinoPageRoute(builder: (_) => FullVideoPage(controller: controller, playType: PlayType.network), fullscreenDialog: true));
196 | setState(() {
197 | controller = newController;
198 | });
199 | },
200 | child: Container(
201 | padding: EdgeInsets.all(20),
202 | child: Image.asset('static/full_screen_on.png', width: 20, height: 20),
203 | ),
204 | ),
205 | ) : SizedBox(),
206 | ],
207 | ),
208 | ),
209 | ),
210 | );
211 | }
212 |
213 | List clearUrlList = [
214 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4',
215 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f20.mp4',
216 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f30.mp4',
217 | ];
218 |
219 | changeClear(int urlIndex, {int? startTime}) {
220 | controller!.removeListener(listener!);
221 | controller!.pause();
222 | controller = TencentPlayerController.network(clearUrlList[urlIndex], playerConfig: PlayerConfig(startTime: startTime ?? controller!.value.position.inSeconds));
223 | controller!.initialize().then((_) {
224 | if (mounted) setState(() {});
225 | });
226 | controller!.addListener(listener!);
227 | }
228 |
229 |
230 | hideCover() {
231 | if (!mounted) return;
232 | setState(() {
233 | showCover = !showCover;
234 | });
235 | delayHideCover();
236 | }
237 |
238 | delayHideCover() {
239 | if (timer != null) {
240 | timer?.cancel();
241 | timer = null;
242 | }
243 | if (showCover) {
244 | timer = new Timer(Duration(seconds: 6), () {
245 | if (!mounted) return;
246 | setState(() {
247 | showCover = false;
248 | });
249 | });
250 | }
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/example/lib/util/common_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | class CommonUtils {
5 |
6 | // 防抖函数: eg:输入框连续输入,用户停止操作300ms才执行访问接口
7 | static const deFaultDurationTime = 300;
8 | static Timer? timer;
9 |
10 | static antiShake(Function? doSomething, {durationTime = deFaultDurationTime}) {
11 | timer!.cancel();
12 | timer = new Timer(Duration(milliseconds: durationTime), () {
13 | doSomething!.call();
14 | timer = null;
15 | });
16 | }
17 |
18 | // 节流函数: eg:300ms内,只会触发一次
19 | static int startTime = 0;
20 |
21 | static throttle(Function? doSomething, {durationTime = deFaultDurationTime}) {
22 | int currentTime = DateTime.now().millisecondsSinceEpoch;
23 | if (currentTime - startTime > durationTime) {
24 | doSomething!.call();
25 | startTime = DateTime.now().millisecondsSinceEpoch;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/example/lib/util/forbidshot_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_forbidshot/flutter_forbidshot.dart';
5 |
6 | class ForbidShotUtil {
7 | static bool? isCapture;
8 | static StreamSubscription? subscription;
9 |
10 | static initForbid(BuildContext context) async {
11 | isCapture = await FlutterForbidshot.iosIsCaptured;
12 | _deal(context);
13 | if (Platform.isIOS) {
14 | subscription = FlutterForbidshot.iosShotChange.listen((event) {
15 | isCapture = !isCapture!;
16 | _deal(context);
17 | });
18 | }
19 | FlutterForbidshot.setAndroidForbidOn();
20 | }
21 |
22 | static _deal(BuildContext context) {
23 | if (isCapture == true) {
24 | Navigator.pop(context);
25 | }
26 | }
27 |
28 | static disposeForbid() {
29 | subscription?.cancel();
30 | subscription = null;
31 | FlutterForbidshot.setAndroidForbidOff();
32 | }
33 | }
--------------------------------------------------------------------------------
/example/lib/util/time_util.dart:
--------------------------------------------------------------------------------
1 | class TimeUtil {
2 | static String formatDuration(Duration position) {
3 | final ms = position.inMilliseconds;
4 |
5 | int seconds = ms ~/ 1000;
6 | final int hours = seconds ~/ 3600;
7 | seconds = seconds % 3600;
8 | var minutes = seconds ~/ 60;
9 | seconds = seconds % 60;
10 |
11 | final hoursString = hours >= 10 ? '$hours' : hours == 0 ? '00' : '0$hours';
12 |
13 | final minutesString =
14 | minutes >= 10 ? '$minutes' : minutes == 0 ? '00' : '0$minutes';
15 |
16 | final secondsString =
17 | seconds >= 10 ? '$seconds' : seconds == 0 ? '00' : '0$seconds';
18 |
19 | final formattedTime =
20 | '${hoursString == '00' ? '' : hoursString + ':'}$minutesString:$secondsString';
21 |
22 | return formattedTime;
23 | }
24 | }
--------------------------------------------------------------------------------
/example/lib/util/widget_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class WidgetUtil {
4 | static Rect? findGlobalRect(GlobalKey key) {
5 | RenderBox renderObject = key.currentContext?.findRenderObject() as RenderBox;
6 | if (renderObject == null) {
7 | return null;
8 | }
9 |
10 | var globalOffset = renderObject.localToGlobal(Offset.zero);
11 |
12 | if (globalOffset == null) {
13 | return null;
14 | }
15 |
16 | var bounds = renderObject.paintBounds;
17 | bounds = bounds.translate(globalOffset.dx, globalOffset.dy);
18 | return bounds;
19 | }
20 |
21 | static Offset? globalOffsetToLocal(GlobalKey key, Offset offsetGlobal) {
22 | RenderBox renderObject = key.currentContext?.findRenderObject() as RenderBox;
23 | if (renderObject == null) {
24 | return null;
25 | }
26 | return renderObject.globalToLocal(offsetGlobal);
27 | }
28 | }
--------------------------------------------------------------------------------
/example/lib/widget/tencent_player_gesture_cover.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_forbidshot/flutter_forbidshot.dart';
3 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
4 | import 'package:flutter_tencentplayer_example/util/common_util.dart';
5 | import 'package:flutter_tencentplayer_example/util/time_util.dart';
6 | import 'package:flutter_tencentplayer_example/util/widget_util.dart';
7 | import 'package:screen/screen.dart';
8 | class TencentPlayerGestureCover extends StatefulWidget {
9 | final TencentPlayerController controller;
10 | final bool showBottomWidget;
11 | final VoidCallback? behavingCallBack; //正在交互
12 |
13 | TencentPlayerGestureCover({
14 | required this.controller,
15 | this.showBottomWidget = true,
16 | this.behavingCallBack,
17 | });
18 |
19 | @override
20 | _TencentPlayerGestureCoverState createState() => _TencentPlayerGestureCoverState();
21 | }
22 |
23 | class _TencentPlayerGestureCoverState extends State {
24 | GlobalKey currentKey = GlobalKey();
25 | TencentPlayerController get controller => widget.controller;
26 |
27 | bool _controllerWasPlaying = false;
28 | bool showSeekText = false;
29 | bool? leftVerticalDrag;
30 |
31 | Duration? seekPos;
32 |
33 | //UI
34 | IconData iconData = Icons.volume_up;
35 | String text = '';
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | Duration? showDuration = seekPos != null ? seekPos : controller.value.position;
40 |
41 | return GestureDetector(
42 | key: currentKey,
43 | behavior: HitTestBehavior.opaque,
44 | child: Center(
45 | child: Column(
46 | mainAxisSize: MainAxisSize.min,
47 | children: [
48 | ///跳转进度
49 | showSeekText && widget.showBottomWidget ? Padding(
50 | padding: EdgeInsets.only(bottom: MediaQuery.of(context).size.width / 3),
51 | child: Container(
52 | width: 150,
53 | height: 50,
54 | alignment: Alignment.center,
55 | decoration: BoxDecoration(
56 | color: Color(0x7f000000),
57 | borderRadius: BorderRadius.all(Radius.circular(10.0)),
58 | ),
59 | child: Row(
60 | mainAxisSize: MainAxisSize.min,
61 | children: [
62 | Text(TimeUtil.formatDuration(showDuration!), style: TextStyle(
63 | color: Color(0xfffe373c),
64 | fontSize: 18,
65 | ),),
66 | Text('/' + TimeUtil.formatDuration(controller.value.duration), style: TextStyle(
67 | color: Colors.white,
68 | fontSize: 18,
69 | ),),
70 | ],
71 | ),
72 | ),
73 | ): SizedBox(),
74 | ///亮度and音量
75 | leftVerticalDrag != null ? Container(
76 | width: 100,
77 | height: 100,
78 | alignment: Alignment.center,
79 | decoration: BoxDecoration(
80 | color: Color(0x7f000000),
81 | borderRadius: BorderRadius.all(Radius.circular(10.0)),
82 | ),
83 | child: Column(
84 | mainAxisSize: MainAxisSize.min,
85 | children: [
86 | Icon(iconData, color: Color(0x88FE373C), size: 25,),
87 | Padding(
88 | padding: EdgeInsets.all(5.0),
89 | child: Text(text, style: TextStyle(
90 | color: Color(0x88FE373C),
91 | fontSize: 18,
92 | )),
93 | ),
94 | ],
95 | ),
96 | ): SizedBox()
97 | ],
98 | ),
99 | ),
100 | onHorizontalDragStart: (DragStartDetails details) {
101 | if (!controller.value.initialized || !widget.showBottomWidget) {
102 | return;
103 | }
104 | _controllerWasPlaying = controller.value.isPlaying;
105 | if (_controllerWasPlaying) {
106 | controller.pause();
107 | }
108 | setState(() {
109 | seekPos = controller.value.position;
110 | showSeekText = true;
111 | });
112 | },
113 | onHorizontalDragUpdate: (DragUpdateDetails details) {
114 | if (!controller.value.initialized || !widget.showBottomWidget) {
115 | return;
116 | }
117 | seekToAbsolutePosition(details.delta);
118 | },
119 | onHorizontalDragEnd: (DragEndDetails details) async {
120 | if (!widget.showBottomWidget) {
121 | return;
122 | }
123 | await controller.seekTo(seekPos!);
124 | seekPos = null;
125 | if (_controllerWasPlaying) {
126 | controller.play();
127 | }
128 | setState(() {
129 | showSeekText = false;
130 | });
131 | },
132 | onVerticalDragStart: _onVerticalDragStart,
133 | onVerticalDragUpdate: _onVerticalDragUpdate,
134 | onVerticalDragEnd: _onVerticalDragEnd,
135 | );
136 | }
137 |
138 | void seekToAbsolutePosition(Offset delta) {
139 | if (seekPos == null) return;
140 | seekPos = seekPos! + Duration(milliseconds: 800) * delta.dx;
141 | if (seekPos! < Duration()) {
142 | seekPos = Duration();
143 | } else if (seekPos! > controller.value.duration) {
144 | seekPos = controller.value.duration;
145 | }
146 | if (mounted) setState(() {});
147 | /// 回调正在交互,用来做延迟隐藏cover
148 | CommonUtils.throttle(() {
149 | widget.behavingCallBack?.call();
150 | });
151 | }
152 |
153 |
154 | double currentVolume = 0.0;
155 | _onVerticalDragStart(DragStartDetails details) async {
156 | double width = WidgetUtil.findGlobalRect(currentKey)!.width;
157 | double xOffSet = WidgetUtil.globalOffsetToLocal(currentKey, details.globalPosition)!.dx;
158 | leftVerticalDrag = xOffSet / width <= 0.5;
159 | if (leftVerticalDrag == false) {
160 | currentVolume = await FlutterForbidshot.volume;
161 | }
162 | }
163 |
164 | _onVerticalDragUpdate(DragUpdateDetails details) async {
165 | if (leftVerticalDrag == true) {
166 | double targetBright = ((await Screen.brightness) - details.delta.dy * 0.01).clamp(0.0, 1.0);
167 | Screen.setBrightness(targetBright);
168 |
169 | if (targetBright >= 0.66) {
170 | iconData = Icons.brightness_high;
171 | } else if(targetBright < 0.66 && targetBright > 0.33) {
172 | iconData = Icons.brightness_medium;
173 | } else {
174 | iconData = Icons.brightness_low;
175 | }
176 |
177 | text = (targetBright * 100).toStringAsFixed(0);
178 | if (mounted) setState(() {});
179 |
180 | CommonUtils.throttle(() {
181 | widget.behavingCallBack?.call();
182 | });
183 | } else if (leftVerticalDrag == false) {
184 | double targetVolume = (currentVolume - details.delta.dy * 0.01).clamp(0.0, 1.0);
185 | FlutterForbidshot.setVolume(targetVolume);
186 |
187 | if (targetVolume >= 0.66) {
188 | iconData = Icons.volume_up;
189 | } else if(targetVolume < 0.66 && targetVolume > 0.33) {
190 | iconData = Icons.volume_down;
191 | } else {
192 | iconData = Icons.volume_mute;
193 | }
194 | currentVolume = targetVolume;
195 | text = (targetVolume * 100).toStringAsFixed(0);
196 | if (mounted) setState(() {});
197 |
198 | CommonUtils.throttle(() {
199 | widget.behavingCallBack?.call();
200 | });
201 | }
202 |
203 | }
204 |
205 | _onVerticalDragEnd(DragEndDetails details) {
206 | leftVerticalDrag = null;
207 | }
208 |
209 | }
--------------------------------------------------------------------------------
/example/lib/widget/tencent_player_loading.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
3 |
4 |
5 | class TencentPlayerLoading extends StatelessWidget {
6 | TencentPlayerController? controller;
7 | double? iconW;
8 |
9 | TencentPlayerLoading({this.controller, this.iconW});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | String tip = '';
14 | if (!controller!.value.initialized && controller!.value.errorDescription == null) {
15 | tip = '加载中...';
16 | } else if (controller!.value.errorDescription != null) {
17 | tip = controller!.value.errorDescription!;
18 | } else if(controller!.value.isLoading) {
19 | tip = '${controller!.value.netSpeed}kb/s';
20 | }
21 | if (!controller!.value.initialized || controller!.value.errorDescription != null || controller!.value.isLoading) {
22 | return Column(
23 | mainAxisSize: MainAxisSize.min,
24 | children: [
25 | Image.asset('static/video_loading.png', width: this.iconW ?? _Style.loadingW, height: this.iconW ??_Style.loadingW,),
26 | SizedBox(height: 8,),
27 | Text(tip, style: TextStyle(
28 | color: Colors.white,
29 | fontSize: 10,
30 | ),),
31 | ],
32 | );
33 | } else {
34 | return SizedBox();
35 | }
36 | }
37 | }
38 |
39 |
40 | class _Style {
41 | static double containerH = 211;
42 | static double fullScreenOnW = 21;
43 | static double loadingW = 30;
44 | }
45 |
--------------------------------------------------------------------------------
/example/lib/widget/triangle_painter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TrianglePainter extends CustomPainter {
4 | late Paint mPaint;
5 | final BuildContext mContext;
6 | TrianglePainter(this.mContext) {
7 | mPaint = new Paint();
8 | mPaint.style = PaintingStyle.fill;
9 | mPaint.color = Color(0x7f000000);
10 | mPaint.isAntiAlias = true;
11 |
12 | }
13 |
14 | @override
15 | void paint(Canvas canvas, Size size) {
16 | Path path = new Path();
17 | path.moveTo(0, 0);// 此点为多边形的起点
18 | path.lineTo(6, 6);
19 | path.lineTo(12, 0);
20 | path.close();
21 | canvas.drawPath(path, mPaint);
22 | }
23 |
24 | @override
25 | bool shouldRepaint(CustomPainter oldDelegate) {
26 | return true;
27 | }
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.8.1"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.3.1"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | cupertino_icons:
47 | dependency: "direct main"
48 | description:
49 | name: cupertino_icons
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "0.1.3"
53 | fake_async:
54 | dependency: transitive
55 | description:
56 | name: fake_async
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "1.2.0"
60 | flutter:
61 | dependency: "direct main"
62 | description: flutter
63 | source: sdk
64 | version: "0.0.0"
65 | flutter_forbidshot:
66 | dependency: "direct dev"
67 | description:
68 | name: flutter_forbidshot
69 | url: "https://pub.flutter-io.cn"
70 | source: hosted
71 | version: "0.0.2"
72 | flutter_plugin_android_lifecycle:
73 | dependency: transitive
74 | description:
75 | name: flutter_plugin_android_lifecycle
76 | url: "https://pub.flutter-io.cn"
77 | source: hosted
78 | version: "1.0.11"
79 | flutter_tencentplayer:
80 | dependency: "direct dev"
81 | description:
82 | path: ".."
83 | relative: true
84 | source: path
85 | version: "1.2.0"
86 | flutter_test:
87 | dependency: "direct dev"
88 | description: flutter
89 | source: sdk
90 | version: "0.0.0"
91 | http:
92 | dependency: transitive
93 | description:
94 | name: http
95 | url: "https://pub.flutter-io.cn"
96 | source: hosted
97 | version: "0.12.2"
98 | http_parser:
99 | dependency: transitive
100 | description:
101 | name: http_parser
102 | url: "https://pub.flutter-io.cn"
103 | source: hosted
104 | version: "3.1.4"
105 | image_picker:
106 | dependency: "direct dev"
107 | description:
108 | name: image_picker
109 | url: "https://pub.flutter-io.cn"
110 | source: hosted
111 | version: "0.6.7+7"
112 | image_picker_platform_interface:
113 | dependency: transitive
114 | description:
115 | name: image_picker_platform_interface
116 | url: "https://pub.flutter-io.cn"
117 | source: hosted
118 | version: "1.1.6"
119 | matcher:
120 | dependency: transitive
121 | description:
122 | name: matcher
123 | url: "https://pub.flutter-io.cn"
124 | source: hosted
125 | version: "0.12.10"
126 | meta:
127 | dependency: transitive
128 | description:
129 | name: meta
130 | url: "https://pub.flutter-io.cn"
131 | source: hosted
132 | version: "1.7.0"
133 | path:
134 | dependency: transitive
135 | description:
136 | name: path
137 | url: "https://pub.flutter-io.cn"
138 | source: hosted
139 | version: "1.8.0"
140 | path_provider:
141 | dependency: "direct dev"
142 | description:
143 | name: path_provider
144 | url: "https://pub.flutter-io.cn"
145 | source: hosted
146 | version: "1.5.0"
147 | pedantic:
148 | dependency: transitive
149 | description:
150 | name: pedantic
151 | url: "https://pub.flutter-io.cn"
152 | source: hosted
153 | version: "1.11.1"
154 | platform:
155 | dependency: transitive
156 | description:
157 | name: platform
158 | url: "https://pub.flutter-io.cn"
159 | source: hosted
160 | version: "2.2.1"
161 | plugin_platform_interface:
162 | dependency: transitive
163 | description:
164 | name: plugin_platform_interface
165 | url: "https://pub.flutter-io.cn"
166 | source: hosted
167 | version: "1.0.3"
168 | screen:
169 | dependency: "direct dev"
170 | description:
171 | name: screen
172 | url: "https://pub.flutter-io.cn"
173 | source: hosted
174 | version: "0.0.5"
175 | sky_engine:
176 | dependency: transitive
177 | description: flutter
178 | source: sdk
179 | version: "0.0.99"
180 | source_span:
181 | dependency: transitive
182 | description:
183 | name: source_span
184 | url: "https://pub.flutter-io.cn"
185 | source: hosted
186 | version: "1.8.1"
187 | stack_trace:
188 | dependency: transitive
189 | description:
190 | name: stack_trace
191 | url: "https://pub.flutter-io.cn"
192 | source: hosted
193 | version: "1.10.0"
194 | stream_channel:
195 | dependency: transitive
196 | description:
197 | name: stream_channel
198 | url: "https://pub.flutter-io.cn"
199 | source: hosted
200 | version: "2.1.0"
201 | string_scanner:
202 | dependency: transitive
203 | description:
204 | name: string_scanner
205 | url: "https://pub.flutter-io.cn"
206 | source: hosted
207 | version: "1.1.0"
208 | term_glyph:
209 | dependency: transitive
210 | description:
211 | name: term_glyph
212 | url: "https://pub.flutter-io.cn"
213 | source: hosted
214 | version: "1.2.0"
215 | test_api:
216 | dependency: transitive
217 | description:
218 | name: test_api
219 | url: "https://pub.flutter-io.cn"
220 | source: hosted
221 | version: "0.4.2"
222 | typed_data:
223 | dependency: transitive
224 | description:
225 | name: typed_data
226 | url: "https://pub.flutter-io.cn"
227 | source: hosted
228 | version: "1.3.0"
229 | vector_math:
230 | dependency: transitive
231 | description:
232 | name: vector_math
233 | url: "https://pub.flutter-io.cn"
234 | source: hosted
235 | version: "2.1.0"
236 | sdks:
237 | dart: ">=2.12.0 <3.0.0"
238 | flutter: ">=1.12.13"
239 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_tencentplayer_example
2 | description: Demonstrates how to use the flutter_tencentplayer plugin.
3 | publish_to: 'none'
4 |
5 | environment:
6 | sdk: ">=2.12.0 <3.0.0"
7 |
8 | dependencies:
9 | flutter:
10 | sdk: flutter
11 |
12 | # The following adds the Cupertino Icons font to your application.
13 | # Use with the CupertinoIcons class for iOS style icons.
14 | cupertino_icons: ^0.1.2
15 |
16 | dev_dependencies:
17 | flutter_test:
18 | sdk: flutter
19 |
20 | flutter_tencentplayer:
21 | path: ../
22 |
23 | screen: 0.0.5
24 | flutter_forbidshot: 0.0.2
25 | path_provider: 1.5.0
26 | image_picker: 0.6.7+7
27 |
28 | # For information on the generic Dart part of this file, see the
29 | # following page: https://dart.dev/tools/pub/pubspec
30 |
31 | # The following section is specific to Flutter.
32 | flutter:
33 |
34 | # The following line ensures that the Material Icons font is
35 | # included with your application, so that you can use the icons in
36 | # the material Icons class.
37 | uses-material-design: true
38 |
39 | # To add assets to your application, add an assets section, like this:
40 | # assets:
41 | # - images/a_dot_burr.jpeg
42 | # - images/a_dot_ham.jpeg
43 |
44 | # An image asset can refer to one or more resolution-specific "variants", see
45 | # https://flutter.dev/assets-and-images/#resolution-aware.
46 |
47 | # For details regarding adding assets from package dependencies, see
48 | # https://flutter.dev/assets-and-images/#from-packages
49 |
50 | # To add custom fonts to your application, add a fonts section here,
51 | # in this "flutter" section. Each entry in this list should have a
52 | # "family" key with the font family name, and a "fonts" key with a
53 | # list giving the asset and other descriptors for the font. For
54 | # example:
55 | # fonts:
56 | # - family: Schyler
57 | # fonts:
58 | # - asset: fonts/Schyler-Regular.ttf
59 | # - asset: fonts/Schyler-Italic.ttf
60 | # style: italic
61 | # - family: Trajan Pro
62 | # fonts:
63 | # - asset: fonts/TrajanPro.ttf
64 | # - asset: fonts/TrajanPro_Bold.ttf
65 | # weight: 700
66 | #
67 | # For details regarding fonts from package dependencies,
68 | # see https://flutter.dev/custom-fonts/#from-packages
69 |
70 |
71 | assets:
72 | - static/tencent1.mp4
73 | - static/icon_back.png
74 | - static/place_nodata.png
75 | - static/player_lock.png
76 | - static/player_pause.png
77 | - static/player_play.png
78 | - static/player_progress_img.png
79 | - static/player_rotate.png
80 | - static/player_unlock.png
81 | - static/video_loading.png
82 | - static/full_screen_on.png
83 |
--------------------------------------------------------------------------------
/example/static/full_screen_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/full_screen_on.png
--------------------------------------------------------------------------------
/example/static/icon_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/icon_back.png
--------------------------------------------------------------------------------
/example/static/place_nodata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/place_nodata.png
--------------------------------------------------------------------------------
/example/static/player_lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/player_lock.png
--------------------------------------------------------------------------------
/example/static/player_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/player_pause.png
--------------------------------------------------------------------------------
/example/static/player_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/player_play.png
--------------------------------------------------------------------------------
/example/static/player_progress_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/player_progress_img.png
--------------------------------------------------------------------------------
/example/static/player_rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/player_rotate.png
--------------------------------------------------------------------------------
/example/static/player_unlock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/player_unlock.png
--------------------------------------------------------------------------------
/example/static/tencent1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/tencent1.mp4
--------------------------------------------------------------------------------
/example/static/video_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/example/static/video_loading.png
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flutter_tencentplayer_example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Verify Platform version', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that platform version is retrieved.
19 | expect(
20 | find.byWidgetPredicate(
21 | (Widget? widget) => widget is Text &&
22 | widget!.data!.startsWith('Running on:'),
23 | ),
24 | findsOneWidget,
25 | );
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/flutter_tencentplayer.iml:
--------------------------------------------------------------------------------
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 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq326646683/flutter_tencentplayer/bcce6a3937882191008155c4bded1e4ac36a244a/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/ios/Classes/FlutterTencentplayerPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface FlutterTencentplayerPlugin : NSObject
4 | @end
5 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterTencentplayerPlugin.m:
--------------------------------------------------------------------------------
1 | #import "FlutterTencentplayerPlugin.h"
2 |
3 | #import "TencentVideoPlayer.h"
4 | #import "TencentFrameUpdater.h"
5 | #import "TencentDownLoadManager.h"
6 |
7 | @interface FlutterTencentplayerPlugin ()
8 |
9 | @property(readonly, nonatomic) NSObject* registry;
10 | @property(readonly, nonatomic) NSObject* messenger;
11 | @property(readonly, nonatomic) NSMutableDictionary* players;
12 | @property(readonly, nonatomic) NSMutableDictionary* downLoads;
13 | @property(readonly, nonatomic) NSObject* registrar;
14 |
15 |
16 |
17 |
18 | @end
19 |
20 |
21 | @implementation FlutterTencentplayerPlugin
22 |
23 | NSObject* mRegistrar;
24 | //TencentVideoPlayer* player ;
25 |
26 | - (instancetype)initWithRegistrar:(NSObject*)registrar {
27 | self = [super init];
28 | NSAssert(self, @"super init cannot be nil");
29 | _registry = [registrar textures];
30 | _messenger = [registrar messenger];
31 | _registrar = registrar;
32 | _players = [NSMutableDictionary dictionaryWithCapacity:1];
33 | _downLoads = [NSMutableDictionary dictionaryWithCapacity:1];
34 | NSLog(@"FLTVideo initWithRegistrar");
35 | return self;
36 | }
37 |
38 | + (void)registerWithRegistrar:(NSObject*)registrar {
39 | FlutterMethodChannel* channel = [FlutterMethodChannel
40 | methodChannelWithName:@"flutter_tencentplayer"
41 | binaryMessenger:[registrar messenger]];
42 | // FlutterTencentplayerPlugin* instance = [[FlutterTencentplayerPlugin alloc] init];
43 | FlutterTencentplayerPlugin* instance = [[FlutterTencentplayerPlugin alloc] initWithRegistrar:registrar];
44 |
45 | [registrar addMethodCallDelegate:instance channel:channel];
46 |
47 |
48 | }
49 |
50 | - (void)detachFromEngineForRegistrar:(NSObject*)registrar {
51 | [self disposeAllPlayers];
52 | }
53 |
54 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
55 | //NSLog(@"FLTVideo call name %@",call.method);
56 | if ([@"init" isEqualToString:call.method]) {
57 | [self disposeAllPlayers];
58 | result(nil);
59 | }else if([@"create" isEqualToString:call.method]){
60 | NSLog(@"FLTVideo create");
61 | [self disposeAllPlayers];
62 | TencentFrameUpdater* frameUpdater = [[TencentFrameUpdater alloc] initWithRegistry:_registry];
63 | TencentVideoPlayer*
64 | player= [[TencentVideoPlayer alloc] initWithCall:call frameUpdater:frameUpdater registry:_registry messenger:_messenger];
65 | if (player) {
66 | [self onPlayerSetup:player frameUpdater:frameUpdater result:result];
67 | }
68 | result(nil);
69 | }else if([@"download" isEqualToString:call.method]){
70 |
71 | NSDictionary* argsMap = call.arguments;
72 | NSString* urlOrFileId = argsMap[@"urlOrFileId"];
73 | NSLog(@"下载相关 startdownload %@", urlOrFileId);
74 |
75 | NSString* channelUrl =[NSString stringWithFormat:@"flutter_tencentplayer/downloadEvents%@",urlOrFileId];
76 | NSLog(@"%@", channelUrl);
77 | FlutterEventChannel* eventChannel = [FlutterEventChannel
78 | eventChannelWithName:channelUrl
79 | binaryMessenger:_messenger];
80 | TencentDownLoadManager* downLoadManager = [[TencentDownLoadManager alloc] initWithMethodCall:call result:result];
81 | [eventChannel setStreamHandler:downLoadManager];
82 | downLoadManager.eventChannel =eventChannel;
83 | [downLoadManager downLoad];
84 |
85 | _downLoads[urlOrFileId] = downLoadManager;
86 | NSLog(@"下载相关 start 数组大小 %lu", (unsigned long)_downLoads.count);
87 |
88 |
89 | result(nil);
90 | }else if([@"stopDownload" isEqualToString:call.method]){
91 | NSDictionary* argsMap = call.arguments;
92 | NSString* urlOrFileId = argsMap[@"urlOrFileId"];
93 | NSLog(@"下载相关 stopDownload %@", urlOrFileId);
94 | TencentDownLoadManager* downLoadManager = _downLoads[urlOrFileId];
95 | if(downLoadManager!=nil){
96 | [downLoadManager stopDownLoad];
97 | }else{
98 | NSLog(@"下载相关 对象为空 %lu", (unsigned long)_downLoads.count);
99 | }
100 |
101 |
102 |
103 | result(nil);
104 | }else {
105 | [self onMethodCall:call result:result];
106 | }
107 | }
108 |
109 | -(void) onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
110 |
111 | NSDictionary* argsMap = call.arguments;
112 | // int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue;
113 | if([NSNull null]==argsMap[@"textureId"]) {
114 | return;
115 | }
116 | int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue;
117 | TencentVideoPlayer* player = _players[@(textureId)];
118 |
119 | if([@"play" isEqualToString:call.method]){
120 | [player resume];
121 | result(nil);
122 | }else if([@"pause" isEqualToString:call.method]){
123 | [player pause];
124 | result(nil);
125 | }else if([@"seekTo" isEqualToString:call.method]){
126 | NSLog(@"跳转到指定位置----------");
127 | [player seekTo:[[argsMap objectForKey:@"location"] intValue]];
128 | result(nil);
129 | }else if([@"setRate" isEqualToString:call.method]){ //播放速率
130 | NSLog(@"修改播放速率----------");
131 | float rate = [[argsMap objectForKey:@"rate"] floatValue];
132 | if (rate<0||rate>2) {
133 | result(nil);
134 | return;
135 | }
136 | [player setRate:rate];
137 | result(nil);
138 |
139 | }else if([@"setBitrateIndex" isEqualToString:call.method]){
140 | NSLog(@"修改播放清晰度----------");
141 | int index = [[argsMap objectForKey:@"index"] intValue];
142 | [player setBitrateIndex:index];
143 | result(nil);
144 | }else if([@"dispose" isEqualToString:call.method]){
145 | NSLog(@"FLTVideo dispose ---- ");
146 | [player dispose];
147 | [_registry unregisterTexture:textureId];
148 | [_players removeObjectForKey:@(textureId)];
149 | result(nil);
150 | }else{
151 | result(FlutterMethodNotImplemented);
152 | }
153 |
154 | }
155 |
156 | - (void)onPlayerSetup:(TencentVideoPlayer*)player
157 | frameUpdater:(TencentFrameUpdater*)frameUpdater
158 | result:(FlutterResult)result {
159 | _players[@(player.textureId)] = player;
160 | result(@{@"textureId" : @(player.textureId)});
161 |
162 | }
163 |
164 | -(void) disposeAllPlayers{
165 | NSLog(@"FLTVideo 初始化播放器状态----------");
166 | // Allow audio playback when the Ring/Silent switch is set to silent
167 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
168 | for (NSNumber* textureId in _players) {
169 | [_registry unregisterTexture:[textureId unsignedIntegerValue]];
170 | [[_players objectForKey:textureId] dispose];
171 | }
172 | [_players removeAllObjects];
173 | }
174 | @end
175 |
--------------------------------------------------------------------------------
/ios/Classes/TencentDownLoadManager.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #import
4 | #import
5 | #import "TXLiteAVSDK.h"
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | @interface TencentDownLoadManager : NSObject
9 |
10 |
11 | @property(nonatomic) FlutterEventSink eventSink;
12 | @property(nonatomic) FlutterEventChannel* eventChannel;
13 | @property(nonatomic) FlutterResult result;
14 | @property(nonatomic) FlutterMethodCall* call;
15 | @property(nonatomic) NSString* path;
16 | @property(nonatomic) NSString* urlOrFileId;
17 | @property(nonatomic) TXVodDownloadManager* tXVodDownloadManager;
18 | @property(nonatomic) TXVodDownloadMediaInfo* tempMedia;
19 |
20 | - (instancetype)initWithMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
21 |
22 | //下载的方法
23 | - (void)downLoad;
24 | //停止下载的方法
25 | - (void)stopDownLoad;
26 |
27 | @end
28 |
29 | NS_ASSUME_NONNULL_END
30 |
--------------------------------------------------------------------------------
/ios/Classes/TencentDownLoadManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // TencentDownLoadManager.m
3 | // flutter_tencentplayer
4 | //
5 | // Created by wilson on 2019/8/16.
6 | //
7 |
8 | #import "TencentDownLoadManager.h"
9 |
10 | @implementation TencentDownLoadManager
11 |
12 |
13 | - (instancetype)initWithMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{
14 | _call = call;
15 | _result = result;
16 |
17 | // [_eventChannel setStreamHandler:self];
18 | NSDictionary* argsMap = _call.arguments;
19 | _path = argsMap[@"savePath"];
20 |
21 | NSLog(@"下载地址 %@", _path);
22 | _urlOrFileId = argsMap[@"urlOrFileId"];
23 | if (_tXVodDownloadManager == nil) {
24 | _tXVodDownloadManager = [TXVodDownloadManager shareInstance];
25 | // NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
26 | // NSString *docPath = [paths lastObject];
27 | // NSString *downloadPath = [docPath stringByAppendingString:@"/downloader" ];
28 | //// NSLog(downloadPath);
29 | [_tXVodDownloadManager setDownloadPath:_path];
30 | }
31 | _tXVodDownloadManager.delegate = self;
32 | return self;
33 | }
34 |
35 | //开始下载
36 | - (void)downLoad{
37 | //设置下载对象
38 | NSLog(@"开始下载");
39 | if([_urlOrFileId hasPrefix: @"http"]){
40 | _tempMedia = [_tXVodDownloadManager startDownloadUrl:_urlOrFileId];
41 | }else{
42 | NSDictionary* argsMap = _call.arguments;
43 | int appId = [argsMap[@"appId"] intValue];
44 | int quanlity = [argsMap[@"quanlity"] intValue];
45 | _urlOrFileId = argsMap[@"urlOrFileId"];
46 | TXPlayerAuthParams *auth = [TXPlayerAuthParams new];
47 | auth.appId =appId;
48 | auth.fileId = _urlOrFileId;
49 | TXVodDownloadDataSource *dataSource = [TXVodDownloadDataSource new];
50 | dataSource.auth = auth;
51 | dataSource.templateName = @"HLS-标清-SD";
52 | if (quanlity == 2) {
53 | dataSource.templateName = @"HLS-标清-SD";
54 | } else if (quanlity == 3) {
55 | dataSource.templateName = @"HLS-高清-HD";
56 | } else if (quanlity == 4) {
57 | dataSource.templateName = @"HLS-全高清-FHD";
58 | }
59 | _tempMedia = [_tXVodDownloadManager startDownload:dataSource];
60 | }
61 |
62 | }
63 |
64 |
65 | //停止下载
66 | - (void)stopDownLoad{
67 | NSLog(@"停止下载");
68 | [_tXVodDownloadManager stopDownload:_tempMedia];
69 | }
70 |
71 | // ---------------通信相关
72 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments {
73 | _eventSink = nil;
74 |
75 | NSLog(@"TencentDownLoadManager停止通信");
76 | return nil;
77 | }
78 |
79 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events {
80 | _eventSink = events;
81 | NSLog(@"TencentDownLoadManager设置全局通信");
82 | return nil;
83 | }
84 |
85 |
86 |
87 | //----------------下载回调相关
88 |
89 | - (void)onDownloadStart:(TXVodDownloadMediaInfo *)mediaInfo {
90 | [self dealCallToFlutterData:@"start" mediaInfo:mediaInfo ];
91 | }
92 |
93 | - (void)onDownloadProgress:(TXVodDownloadMediaInfo *)mediaInfo {
94 |
95 | [self dealCallToFlutterData:@"progress" mediaInfo:mediaInfo ];
96 | }
97 |
98 | - (void)onDownloadStop:(TXVodDownloadMediaInfo *)mediaInfo {
99 |
100 | [self dealCallToFlutterData:@"stop" mediaInfo:mediaInfo ];
101 |
102 | }
103 | - (void)onDownloadFinish:(TXVodDownloadMediaInfo *)mediaInfo {
104 |
105 | [self dealCallToFlutterData:@"complete" mediaInfo:mediaInfo ];
106 | }
107 |
108 | - (void)onDownloadError:(TXVodDownloadMediaInfo *)mediaInfo errorCode:(TXDownloadError)code errorMsg:(NSString *)msg {
109 |
110 | NSLog(@"onDownloadError");
111 |
112 | NSString *quality = [NSString stringWithFormat:@"%ld",(long)mediaInfo.dataSource.quality];
113 | NSString *duration = [NSString stringWithFormat:@"%d",mediaInfo.duration];
114 | NSString *size = [NSString stringWithFormat:@"%d",mediaInfo.size];
115 | NSString *downloadSize = [NSString stringWithFormat:@"%d",mediaInfo.downloadSize];
116 | NSString *progress = [NSString stringWithFormat:@"%f",mediaInfo.progress];
117 |
118 | if(self->_eventSink != nil){
119 | NSMutableDictionary* paramDic = [NSMutableDictionary dictionary];
120 | [paramDic setValue:@"error" forKey:@"downloadStatus"];
121 | [paramDic setValue:quality forKey:@"quanlity"];
122 | [paramDic setValue:duration forKey:@"duration"];
123 | [paramDic setValue:size forKey:@"size"];
124 | [paramDic setValue:downloadSize forKey:@"downloadSize"];
125 | [paramDic setValue:progress forKey:@"progress"];
126 | [paramDic setValue:mediaInfo.playPath forKey:@"playPath"];
127 | [paramDic setValue:@(true) forKey:@"isStop"];
128 | [paramDic setValue:mediaInfo.url forKey:@"url"];
129 | if (mediaInfo.dataSource != nil) {
130 | [paramDic setValue:mediaInfo.dataSource.auth.fileId forKey:@"fileId"];
131 | }
132 | [paramDic setValue:msg forKey:@"error"];
133 |
134 | self->_eventSink(paramDic);
135 | }
136 |
137 | }
138 |
139 | - (int)hlsKeyVerify:(TXVodDownloadMediaInfo *)mediaInfo url:(NSString *)url data:(NSData *)data {
140 | NSLog(@"停止下载");
141 | return 0;
142 | }
143 |
144 | - (void)dealCallToFlutterData:(NSString*)type mediaInfo:(TXVodDownloadMediaInfo *)mediaInfo {
145 | NSLog(@"下载类型");
146 |
147 | NSString *quality = [NSString stringWithFormat:@"%ld",(long)mediaInfo.dataSource.quality];
148 | NSString *duration = [NSString stringWithFormat:@"%d",mediaInfo.duration];
149 | NSString *size = [NSString stringWithFormat:@"%d",mediaInfo.size];
150 | NSString *downloadSize = [NSString stringWithFormat:@"%d",mediaInfo.downloadSize];
151 | NSString *progress = [NSString stringWithFormat:@"%f",mediaInfo.progress];
152 |
153 |
154 | if (self->_eventSink != nil) {
155 | NSMutableDictionary* paramDic = [NSMutableDictionary dictionary];
156 | [paramDic setValue:type forKey:@"downloadStatus"];
157 | [paramDic setValue:quality forKey:@"quality"];
158 | [paramDic setValue:duration forKey:@"duration"];
159 | [paramDic setValue:size forKey:@"size"];
160 | [paramDic setValue:downloadSize forKey:@"downloadSize"];
161 | [paramDic setValue:progress forKey:@"progress"];
162 | [paramDic setValue:mediaInfo.playPath forKey:@"playPath"];
163 | [paramDic setValue:@(true) forKey:@"isStop"];
164 | [paramDic setValue:mediaInfo.url forKey:@"url"];
165 | [paramDic setValue:@"error" forKey:@"error"];
166 | if (mediaInfo.dataSource != nil) {
167 | [paramDic setValue:mediaInfo.dataSource.auth.fileId forKey:@"fileId"];
168 | }
169 |
170 |
171 | self->_eventSink(paramDic);
172 | }
173 |
174 | }
175 |
176 |
177 |
178 |
179 |
180 | @end
181 |
--------------------------------------------------------------------------------
/ios/Classes/TencentFrameUpdater.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 | #import
4 |
5 | NS_ASSUME_NONNULL_BEGIN
6 |
7 | @interface TencentFrameUpdater : NSObject
8 | @property(nonatomic) int64_t textureId;
9 | @property(nonatomic, readonly) NSObject* registry;
10 |
11 | -(void)refreshDisplay;
12 | - (TencentFrameUpdater*)initWithRegistry:(NSObject*)registry;
13 | @end
14 |
15 | NS_ASSUME_NONNULL_END
16 |
--------------------------------------------------------------------------------
/ios/Classes/TencentFrameUpdater.m:
--------------------------------------------------------------------------------
1 |
2 | #import "TencentFrameUpdater.h"
3 |
4 | @implementation TencentFrameUpdater
5 | - (TencentFrameUpdater*)initWithRegistry:(NSObject*)registry {
6 | NSAssert(self, @"super init cannot be nil");
7 | if (self == nil) return nil;
8 | _registry = registry;
9 | return self;
10 | }
11 |
12 | -(void)refreshDisplay{
13 | [_registry textureFrameAvailable:self.textureId];
14 | }
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/Classes/TencentVideoPlayer.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import
4 | #import "TencentFrameUpdater.h"
5 | #import "TXLiteAVSDK.h"
6 | #import
7 | NS_ASSUME_NONNULL_BEGIN
8 |
9 | @interface TencentVideoPlayer : NSObject
10 | @property(readonly,nonatomic) TXVodPlayer* txPlayer;
11 | @property(nonatomic) FlutterEventChannel* eventChannel;
12 |
13 | //ios主动和flutter通信
14 | @property(nonatomic) FlutterEventSink eventSink;
15 | @property(nonatomic, readonly) bool disposed;
16 | @property(nonatomic, readonly) int64_t textureId;
17 |
18 |
19 | /**
20 | * 是否循环播放
21 | */
22 | @property (nonatomic, assign) BOOL loop;
23 | @property(nonatomic)TencentFrameUpdater* frameUpdater;
24 |
25 | - (instancetype)initWithCall:(FlutterMethodCall*)call
26 | frameUpdater:(TencentFrameUpdater*)frameUpdater
27 | registry:(NSObject*)registry
28 | messenger:(NSObject*)messenger;
29 | - (void)dispose;
30 | -(void)resume;
31 | -(void)pause;
32 | -(int64_t)position;
33 | -(int64_t)duration;
34 | -(void)seekTo:(int)position;
35 | /**
36 | * 设置播放开始时间
37 | * 在startPlay前设置,修改开始播放的起始位置
38 | */
39 | - (void)setStartTime:(CGFloat)startTime;
40 |
41 | /**
42 | * 停止播放音视频流
43 | * @return 0 = OK
44 | */
45 | - (int)stopPlay;
46 | /**
47 | * 可播放时长
48 | */
49 | - (float)playableDuration;
50 | /**
51 | * 视频宽度
52 | */
53 | - (int)width;
54 |
55 | /**
56 | * 视频高度
57 | */
58 | - (int)height;
59 | /**
60 | * 设置画面的方向
61 | * @param rotation 方向
62 | * @see TX_Enum_Type_HomeOrientation
63 | */
64 | - (void)setRenderRotation:(TX_Enum_Type_HomeOrientation)rotation;
65 | /**
66 | * 设置画面的裁剪模式
67 | * @param renderMode 裁剪
68 | * @see TX_Enum_Type_RenderMode
69 | */
70 | - (void)setRenderMode:(TX_Enum_Type_RenderMode)renderMode;
71 | /**
72 | * 设置静音
73 | */
74 | - (void)setMute:(BOOL)bEnable;
75 |
76 | /*
77 | * 截屏
78 | * @param snapshotCompletionBlock 通过回调返回当前图像
79 | */
80 | - (void)snapshot:(void (^)(UIImage *))snapshotCompletionBlock;
81 | /**
82 | * 设置播放速率
83 | * @param rate 正常速度为1.0;小于为慢速;大于为快速。最大建议不超过2.0
84 | */
85 | - (void)setRate:(float)rate;
86 | // 设置播放清晰度
87 | - (void)setBitrateIndex:(int)index;
88 | /**
89 | * 设置画面镜像
90 | */
91 | - (void)setMirror:(BOOL)isMirror;
92 |
93 | @end
94 |
95 | NS_ASSUME_NONNULL_END
96 |
--------------------------------------------------------------------------------
/ios/Classes/TencentVideoPlayer.m:
--------------------------------------------------------------------------------
1 |
2 |
3 | #import "TencentVideoPlayer.h"
4 | #import
5 |
6 |
7 |
8 | @implementation TencentVideoPlayer{
9 | // CVPixelBufferRef finalPiexelBuffer;
10 | // CVPixelBufferRef pixelBufferNowRef;
11 | CVPixelBufferRef volatile _latestPixelBuffer;
12 | CVPixelBufferRef _lastBuffer;
13 | //视频自带角度
14 | NSNumber* _degree;
15 | }
16 |
17 | - (instancetype)initWithCall:(FlutterMethodCall *)call frameUpdater:(TencentFrameUpdater *)frameUpdater registry:(NSObject *)registry messenger:(NSObject*)messenger{
18 | self = [super init];
19 | _latestPixelBuffer = nil;
20 | _lastBuffer = nil;
21 | // NSLog(@"FLTVideo 初始化播放器");
22 | _textureId = [registry registerTexture:self];
23 | // NSLog(@"FLTVideo _textureId %lld",_textureId);
24 |
25 | FlutterEventChannel* eventChannel = [FlutterEventChannel
26 | eventChannelWithName:[NSString stringWithFormat:@"flutter_tencentplayer/videoEvents%lld",_textureId]
27 | binaryMessenger:messenger];
28 |
29 |
30 |
31 | _eventChannel = eventChannel;
32 | [_eventChannel setStreamHandler:self];
33 | NSDictionary* argsMap = call.arguments;
34 | TXVodPlayConfig* playConfig = [[TXVodPlayConfig alloc]init];
35 | playConfig.connectRetryCount= 3 ;
36 | playConfig.connectRetryInterval = 3;
37 | playConfig.timeout = 10 ;
38 |
39 |
40 |
41 | id headers = argsMap[@"headers"];
42 | if (headers!=nil&&headers!=NULL&&![@"" isEqualToString:headers]&&headers!=[NSNull null]) {
43 | NSDictionary* headers = argsMap[@"headers"];
44 | playConfig.headers = headers;
45 | }
46 |
47 | id cacheFolderPath = argsMap[@"cachePath"];
48 | if (cacheFolderPath!=nil&&cacheFolderPath!=NULL&&![@"" isEqualToString:cacheFolderPath]&&cacheFolderPath!=[NSNull null]) {
49 | playConfig.cacheFolderPath = cacheFolderPath;
50 | playConfig.maxCacheItems = 2;
51 | }else{
52 | // 设置缓存路径
53 | //playConfig.cacheFolderPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
54 | playConfig.maxCacheItems = 0;
55 | }
56 | playConfig.maxBufferSize=4;
57 |
58 | playConfig.progressInterval = 1;
59 | id pi = argsMap[@"progressInterval"];
60 | if (pi!=nil) {
61 | playConfig.progressInterval = [pi intValue];
62 | }
63 |
64 | BOOL autoPlayArg = [argsMap[@"autoPlay"] boolValue];
65 | float startPosition=0;
66 |
67 | id startTime = argsMap[@"startTime"];
68 | if(startTime!=nil&&startTime!=NULL&&![@"" isEqualToString:startTime]&&startTime!=[NSNull null]){
69 | startPosition =[argsMap[@"startTime"] floatValue];
70 | }
71 |
72 | frameUpdater.textureId = _textureId;
73 | _frameUpdater = frameUpdater;
74 |
75 | _txPlayer = [[TXVodPlayer alloc]init];
76 | [playConfig setPlayerPixelFormatType:kCVPixelFormatType_32BGRA];
77 | [_txPlayer setConfig:playConfig];
78 | [_txPlayer setIsAutoPlay:autoPlayArg];
79 | _txPlayer.enableHWAcceleration = YES;
80 | [_txPlayer setVodDelegate:self];
81 | [_txPlayer setVideoProcessDelegate:self];
82 | [_txPlayer setStartTime:startPosition];
83 |
84 | BOOL loop = [argsMap[@"loop"] boolValue];
85 | [_txPlayer setLoop: loop];
86 |
87 | id pathArg = argsMap[@"uri"];
88 | if(pathArg!=nil&&pathArg!=NULL&&![@"" isEqualToString:pathArg]&&pathArg!=[NSNull null]){
89 | NSLog(@"播放器启动方式1 play");
90 | _degree = [NSNumber numberWithInteger:[self degressFromVideoFileWithURL: pathArg]];
91 | [_txPlayer startPlay:pathArg];
92 | }else{
93 | NSLog(@"播放器启动方式2 fileid");
94 | id auth = argsMap[@"auth"];
95 | if(auth!=nil&&auth!=NULL&&![@"" isEqualToString:auth]&&auth!=[NSNull null]){
96 | NSDictionary* authMap = argsMap[@"auth"];
97 | int appId= [authMap[@"appId"] intValue];
98 | NSString *fileId= authMap[@"fileId"];
99 | NSString *sign= authMap[@"sign"];
100 | TXPlayerAuthParams *p = [TXPlayerAuthParams new];
101 | p.appId = appId;
102 | p.fileId = fileId;
103 | if (sign != nil) {
104 | p.sign = sign;
105 | }
106 | [_txPlayer startPlayWithParams:p];
107 | }
108 | }
109 | NSLog(@"播放器初始化结束");
110 |
111 |
112 | return self;
113 |
114 | }
115 |
116 |
117 | #pragma FlutterTexture
118 | - (CVPixelBufferRef)copyPixelBuffer {
119 | CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
120 | while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil,
121 | (void **)&_latestPixelBuffer)) {
122 | pixelBuffer = _latestPixelBuffer;
123 | }
124 | return pixelBuffer;
125 | }
126 |
127 | #pragma 腾讯播放器代理回调方法
128 | - (BOOL)onPlayerPixelBuffer:(CVPixelBufferRef)pixelBuffer{
129 |
130 | if (_lastBuffer == nil) {
131 | _lastBuffer = CVPixelBufferRetain(pixelBuffer);
132 | CFRetain(pixelBuffer);
133 | } else if (_lastBuffer != pixelBuffer) {
134 | CVPixelBufferRelease(_lastBuffer);
135 | _lastBuffer = CVPixelBufferRetain(pixelBuffer);
136 | CFRetain(pixelBuffer);
137 | }
138 |
139 | CVPixelBufferRef newBuffer = pixelBuffer;
140 |
141 | CVPixelBufferRef old = _latestPixelBuffer;
142 | while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer,
143 | (void **)&_latestPixelBuffer)) {
144 | old = _latestPixelBuffer;
145 | }
146 |
147 | if (old && old != pixelBuffer) {
148 | CFRelease(old);
149 | }
150 | [self.frameUpdater refreshDisplay];
151 | return NO;
152 | }
153 |
154 | /**
155 | * 点播事件通知
156 | *
157 | * @param player 点播对象
158 | * @param EvtID 参见TXLiveSDKEventDef.h
159 | * @param param 参见TXLiveSDKTypeDef.h
160 | * @see TXVodPlayer
161 | */
162 | -(void)onPlayEvent:(TXVodPlayer *)player event:(int)EvtID withParam:(NSDictionary *)param{
163 |
164 | dispatch_async(dispatch_get_main_queue(), ^{
165 | NSMutableDictionary* playEventDic = [NSMutableDictionary dictionary];
166 | [playEventDic setValue:@(EvtID) forKey:@"eventCode"];
167 |
168 | switch (EvtID) {
169 | case PLAY_EVT_VOD_PLAY_PREPARED: {
170 | int64_t duration = [player duration];
171 | NSString *durationStr = [NSString stringWithFormat: @"%ld", (long)duration];
172 | NSInteger durationInt = [durationStr intValue];
173 |
174 | BOOL isRotateAttri = [self ->_degree intValue] == 90 || [self ->_degree intValue] == 270;
175 |
176 | int width = isRotateAttri ? [player height] : [player width];
177 | int height = isRotateAttri ? [player width] : [player height];
178 |
179 |
180 | [playEventDic setValue:@"initialized" forKey:@"event"];
181 | [playEventDic setValue:@(durationInt) forKey:@"duration"];
182 | [playEventDic setValue:@(width) forKey:@"width"];
183 | [playEventDic setValue:@(height) forKey:@"height"];
184 |
185 | if (self->_degree != nil) {
186 | [playEventDic setValue:@([self->_degree integerValue]) forKey:@"degree"];
187 | }
188 | break;
189 | }
190 |
191 | case PLAY_EVT_PLAY_PROGRESS: {
192 | int64_t progress = [player currentPlaybackTime];
193 | int64_t duration = [player duration];
194 | int64_t playableDuration = [player playableDuration];
195 |
196 |
197 | NSString *progressStr = [NSString stringWithFormat: @"%ld", (long)progress];
198 | NSString *durationStr = [NSString stringWithFormat: @"%ld", (long)duration];
199 | NSString *playableDurationStr = [NSString stringWithFormat: @"%ld", (long)playableDuration];
200 | NSInteger progressInt = [progressStr intValue]*1000;
201 | NSInteger durationInt = [durationStr intValue]*1000;
202 | NSInteger playableDurationInt = [playableDurationStr intValue]*1000;
203 |
204 | [playEventDic setValue:@"progress" forKey:@"event"];
205 | [playEventDic setValue:@(progressInt) forKey:@"progress"];
206 | [playEventDic setValue:@(durationInt) forKey:@"duration"];
207 | [playEventDic setValue:@(playableDurationInt) forKey:@"playable"];
208 | break;
209 | }
210 | case PLAY_EVT_PLAY_LOADING:
211 | [playEventDic setValue:@"loading" forKey:@"event"];
212 | break;
213 | case PLAY_EVT_VOD_LOADING_END:
214 | [playEventDic setValue:@"loadingend" forKey:@"event"];
215 | break;
216 | case PLAY_EVT_PLAY_END:
217 | [playEventDic setValue:@"playend" forKey:@"event"];
218 | break;
219 |
220 | default:
221 | break;
222 | }
223 |
224 | if (EvtID < 0) {
225 | [playEventDic setValue:@"error" forKey:@"event"];
226 | [playEventDic setValue:param[@"EVT_MSG"] forKey:@"errorInfo"];
227 | }
228 |
229 | if (self->_eventSink != nil) {
230 | self->_eventSink(playEventDic);
231 | }
232 |
233 | });
234 | }
235 |
236 | - (void)onNetStatus:(TXVodPlayer *)player withParam:(NSDictionary *)param {
237 | if(self->_eventSink!=nil){
238 | self->_eventSink(@{
239 | @"event":@"netStatus",
240 | @"netSpeed": param[NET_STATUS_NET_SPEED],
241 | @"fps": param[NET_STATUS_VIDEO_FPS],
242 | });
243 | }
244 | }
245 |
246 | #pragma FlutterStreamHandler
247 | - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
248 | _eventSink = nil;
249 | NSLog(@"FLTVideo停止通信");
250 | return nil;
251 | }
252 |
253 | - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
254 | eventSink:(nonnull FlutterEventSink)events {
255 | _eventSink = events;
256 |
257 | NSLog(@"FLTVideo开启通信");
258 | //[self sendInitialized];
259 | return nil;
260 | }
261 |
262 | - (void)dispose {
263 | _disposed = true;
264 | [self stopPlay];
265 | _txPlayer = nil;
266 | _frameUpdater = nil;
267 | NSLog(@"FLTVideo dispose");
268 | CVPixelBufferRef old = _latestPixelBuffer;
269 | while (!OSAtomicCompareAndSwapPtrBarrier(old, nil,
270 | (void **)&_latestPixelBuffer)) {
271 | old = _latestPixelBuffer;
272 | }
273 | if (old) {
274 | CFRelease(old);
275 | }
276 |
277 | if (_lastBuffer) {
278 | CVPixelBufferRelease(_lastBuffer);
279 | _lastBuffer = nil;
280 | }
281 |
282 | // if(_eventChannel){
283 | // [_eventChannel setStreamHandler:nil];
284 | // _eventChannel =nil;
285 | // }
286 |
287 | }
288 |
289 | -(void)setLoop:(BOOL)loop{
290 | [_txPlayer setLoop:loop];
291 | _loop = loop;
292 | }
293 |
294 | - (void)resume{
295 | [_txPlayer resume];
296 | }
297 | -(void)pause{
298 | [_txPlayer pause];
299 | }
300 | - (int64_t)position{
301 | return [_txPlayer currentPlaybackTime];
302 | }
303 |
304 | - (int64_t)duration{
305 | return [_txPlayer duration];
306 | }
307 |
308 | - (void)seekTo:(int)position{
309 | [_txPlayer seek:position];
310 | }
311 |
312 | - (void)setStartTime:(CGFloat)startTime{
313 | [_txPlayer setStartTime:startTime];
314 | }
315 |
316 | - (int)stopPlay{
317 | return [_txPlayer stopPlay];
318 | }
319 |
320 | - (float)playableDuration{
321 | return [_txPlayer playableDuration];
322 | }
323 |
324 | - (int)width{
325 | return [_txPlayer width];
326 | }
327 |
328 | - (int)height{
329 | return [_txPlayer height];
330 | }
331 |
332 | - (void)setRenderMode:(TX_Enum_Type_RenderMode)renderMode{
333 | [_txPlayer setRenderMode:renderMode];
334 | }
335 |
336 | - (void)setRenderRotation:(TX_Enum_Type_HomeOrientation)rotation{
337 |
338 | [_txPlayer setRenderRotation:rotation];
339 | }
340 |
341 | - (void)setMute:(BOOL)bEnable{
342 | [_txPlayer setMute:bEnable];
343 | }
344 |
345 |
346 |
347 |
348 | - (void)setRate:(float)rate{
349 | [_txPlayer setRate:rate];
350 | }
351 |
352 | - (void)setBitrateIndex:(int)index{
353 | [_txPlayer setBitrateIndex:index];
354 | }
355 |
356 | - (void)setMirror:(BOOL)isMirror{
357 | [_txPlayer setMirror:isMirror];
358 | }
359 |
360 | -(void)snapshot:(void (^)(UIImage * _Nonnull))snapshotCompletionBlock{
361 |
362 | }
363 |
364 | - (NSUInteger)degressFromVideoFileWithURL:(NSString *)path
365 | {
366 | NSUInteger degress = 0;
367 |
368 | NSArray *tracks;
369 | if ([path hasPrefix: @"http"]) {
370 | AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString: path]];
371 | tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
372 | } else {
373 | AVURLAsset* videoAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath: path] options:nil];
374 | tracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
375 | }
376 | if([tracks count] > 0) {
377 | AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
378 | CGAffineTransform t = videoTrack.preferredTransform;
379 |
380 | if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
381 | // Portrait
382 | degress = 90;
383 | }else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
384 | // PortraitUpsideDown
385 | degress = 270;
386 | }else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
387 | // LandscapeRight
388 | degress = 0;
389 | }else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
390 | // LandscapeLeft
391 | degress = 180;
392 | }
393 | }
394 |
395 | return degress;
396 | }
397 | @end
398 |
--------------------------------------------------------------------------------
/ios/flutter_tencentplayer.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
3 | #
4 | Pod::Spec.new do |s|
5 | s.name = 'flutter_tencentplayer'
6 | s.version = '2.0.0'
7 | s.summary = 'A new Flutter plugin.'
8 | s.description = <<-DESC
9 | A new Flutter plugin.
10 | DESC
11 | s.homepage = 'http://example.com'
12 | s.license = { :file => '../LICENSE' }
13 | s.author = { 'Your Company' => 'email@example.com' }
14 | s.source = { :path => '.' }
15 | s.source_files = 'Classes/**/*'
16 | s.public_header_files = 'Classes/**/*.h'
17 | s.dependency 'Flutter'
18 | # s.dependency 'TXLiteAVSDK_Player'
19 | s.dependency 'TXLiteAVSDK_Player', '= 9.2.10637'
20 | s.user_target_xcconfig = { 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' }
21 | s.static_framework = true
22 | s.ios.deployment_target = '8.0'
23 | end
24 |
25 |
--------------------------------------------------------------------------------
/lib/controller/download_controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
5 |
6 | class DownloadController extends ValueNotifier> {
7 | final String savePath;
8 | final int? appId;
9 | StreamSubscription? _eventSubscription;
10 | MethodChannel channel = TencentPlayer.channel;
11 | bool _isDisposed = false;
12 |
13 | DownloadController(this.savePath, {this.appId}) : super(Map());
14 |
15 | void dowload(String urlOrFileId, {int? quanlity}) async {
16 | Map downloadInfoMap = {
17 | "savePath": savePath,
18 | "urlOrFileId": urlOrFileId,
19 | "appId": appId,
20 | "quanlity": quanlity,
21 | };
22 |
23 | await channel.invokeMethod(
24 | 'download',
25 | downloadInfoMap,
26 | );
27 |
28 | void eventListener(dynamic event) {
29 | if (_isDisposed) {
30 | return;
31 | }
32 | final Map map = event;
33 | debugPrint("native to flutter");
34 | debugPrint(map.toString());
35 | DownloadValue downloadValue = DownloadValue.fromJson(map);
36 | if (downloadValue.fileId != null) {
37 | value[downloadValue.fileId!] = downloadValue;
38 | } else {
39 | value[downloadValue.url!] = downloadValue;
40 | }
41 | notifyListeners();
42 | }
43 |
44 | _eventSubscription = _eventChannelFor(urlOrFileId).receiveBroadcastStream().listen(eventListener);
45 | }
46 |
47 | @override
48 | Future dispose() async {
49 | _isDisposed = true;
50 | super.dispose();
51 | _eventSubscription!.cancel();
52 | }
53 |
54 | Future pauseDownload(String urlOrFileId) async {
55 | if (_isDisposed) {
56 | return;
57 | }
58 | await channel.invokeMethod(
59 | 'stopDownload',
60 | {
61 | "urlOrFileId": urlOrFileId,
62 | },
63 | );
64 | }
65 |
66 | Future cancelDownload(String urlOrFileId) async {
67 | if (_isDisposed) {
68 | return;
69 | }
70 | await channel.invokeMethod(
71 | 'stopDownload',
72 | {
73 | "urlOrFileId": urlOrFileId,
74 | },
75 | );
76 |
77 | if (value.containsKey(urlOrFileId)) {
78 | Future.delayed(Duration(milliseconds: 2500), () {
79 | value.remove(urlOrFileId);
80 | });
81 | }
82 |
83 | notifyListeners();
84 | }
85 |
86 | EventChannel _eventChannelFor(String urlOrFileId) {
87 | return EventChannel('flutter_tencentplayer/downloadEvents$urlOrFileId');
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/controller/tencent_player_controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
5 |
6 | class TencentPlayerController extends ValueNotifier {
7 | int? _textureId;
8 | final String? dataSource;
9 | final DataSourceType dataSourceType;
10 | final PlayerConfig playerConfig;
11 | MethodChannel channel = TencentPlayer.channel;
12 |
13 | TencentPlayerController.asset(this.dataSource,
14 | {this.playerConfig = const PlayerConfig()})
15 | : dataSourceType = DataSourceType.asset,
16 | super(TencentPlayerValue());
17 |
18 | TencentPlayerController.network(this.dataSource,
19 | {this.playerConfig = const PlayerConfig()})
20 | : dataSourceType = DataSourceType.network,
21 | super(TencentPlayerValue());
22 |
23 | TencentPlayerController.file(String filePath,
24 | {this.playerConfig = const PlayerConfig()})
25 | : dataSource = filePath,
26 | dataSourceType = DataSourceType.file,
27 | super(TencentPlayerValue());
28 |
29 | bool _isDisposed = false;
30 | Completer? _creatingCompleter;
31 | StreamSubscription? _eventSubscription;
32 | _VideoAppLifeCycleObserver? _lifeCycleObserver;
33 |
34 | int? get textureId => _textureId;
35 |
36 | Future initialize() async {
37 | if (this.playerConfig.supportBackground == false) {
38 | _lifeCycleObserver = _VideoAppLifeCycleObserver(this);
39 | _lifeCycleObserver!.initialize();
40 | }
41 | _creatingCompleter = Completer();
42 | Map dataSourceDescription;
43 | switch (dataSourceType) {
44 | case DataSourceType.asset:
45 | dataSourceDescription = {'asset': dataSource};
46 | break;
47 | case DataSourceType.network:
48 | case DataSourceType.file:
49 | dataSourceDescription = {'uri': dataSource};
50 | break;
51 | }
52 | value = value.copyWith(isPlaying: playerConfig.autoPlay);
53 | dataSourceDescription.addAll(playerConfig.toJson());
54 | final Map? response =
55 | await channel.invokeMapMethod(
56 | 'create',
57 | dataSourceDescription,
58 | );
59 | _textureId = response!['textureId'];
60 | _creatingCompleter!.complete(null);
61 | final Completer initializingCompleter = Completer();
62 |
63 | void eventListener(dynamic event) {
64 | if (_isDisposed) {
65 | return;
66 | }
67 | final Map map = event;
68 | int? curCode = map['eventCode'];
69 |
70 | switch (map['event']) {
71 | case 'initialized':
72 | value = value.copyWith(
73 | duration: Duration(milliseconds: map['duration']),
74 | size: Size(map['width']?.toDouble() ?? 0.0,
75 | map['height']?.toDouble() ?? 0.0),
76 | degree: map['degree'] ?? 0,
77 | eventCode: curCode,
78 | );
79 | initializingCompleter.complete(null);
80 | break;
81 | case 'progress':
82 | if (!value.isPlaying) return;
83 | Duration newProgress = Duration(milliseconds: map['progress']);
84 | Duration newPlayable = Duration(milliseconds: map['playable']);
85 | if (value.position == newProgress && value.playable == newPlayable)
86 | return;
87 |
88 | value = value.copyWith(
89 | position: newProgress,
90 | duration: Duration(milliseconds: map['duration']),
91 | playable: newPlayable,
92 | eventCode: curCode,
93 | );
94 | break;
95 | case 'loading':
96 | value = value.copyWith(
97 | isLoading: true,
98 | eventCode: curCode,
99 | );
100 | break;
101 | case 'loadingend':
102 | value = value.copyWith(
103 | isLoading: false,
104 | eventCode: curCode,
105 | );
106 | break;
107 | case 'playend':
108 | value = value.copyWith(
109 | isPlaying: false,
110 | position: value.duration,
111 | eventCode: curCode,
112 | );
113 | break;
114 | case 'netStatus':
115 | int fps = map['fps'].toInt();
116 | // 忽略小于3的帧率浮动
117 | if (value.netSpeed == map['netSpeed'] && (value.fps! - fps).abs() < 3) return;
118 | value = value.copyWith(
119 | netSpeed: map['netSpeed'],
120 | fps: fps,
121 | eventCode: curCode,
122 | );
123 | break;
124 | case 'error':
125 | value = value.copyWith(
126 | errorDescription: map['errorInfo'],
127 | eventCode: curCode,
128 | );
129 | break;
130 | case 'orientation':
131 | value = value.copyWith(
132 | orientation: map['orientation'],
133 | eventCode: curCode,
134 | );
135 | break;
136 | default:
137 | value = value.copyWith(
138 | eventCode: curCode,
139 | );
140 | break;
141 | }
142 | }
143 |
144 | _eventSubscription = _eventChannelFor(_textureId!)
145 | .receiveBroadcastStream()
146 | .listen(eventListener);
147 | return initializingCompleter.future;
148 | }
149 |
150 | EventChannel _eventChannelFor(int textureId) {
151 | return EventChannel('flutter_tencentplayer/videoEvents$textureId');
152 | }
153 |
154 | @override
155 | Future dispose() async {
156 | if (_creatingCompleter != null) {
157 | await _creatingCompleter!.future;
158 | if (!_isDisposed) {
159 | _isDisposed = true;
160 | await _eventSubscription?.cancel();
161 | await channel.invokeListMethod(
162 | 'dispose', {'textureId': _textureId});
163 | _lifeCycleObserver?.dispose();
164 | }
165 | }
166 | _isDisposed = true;
167 | super.dispose();
168 | }
169 |
170 | Future play() async {
171 | value = value.copyWith(isPlaying: true);
172 | await _applyPlayPause();
173 | }
174 |
175 | Future pause() async {
176 | value = value.copyWith(isPlaying: false);
177 | await _applyPlayPause();
178 | }
179 |
180 | Future _applyPlayPause() async {
181 | if (!value.initialized || _isDisposed) {
182 | return;
183 | }
184 | if (value.isPlaying) {
185 | await channel
186 | .invokeMethod('play', {'textureId': _textureId});
187 | } else {
188 | await channel
189 | .invokeMethod('pause', {'textureId': _textureId});
190 | }
191 | }
192 |
193 | Future seekTo(Duration moment) async {
194 | if (_isDisposed) {
195 | return;
196 | }
197 | if (moment == null) {
198 | return;
199 | }
200 | if (moment > value.duration) {
201 | moment = value.duration;
202 | } else if (moment < const Duration()) {
203 | moment = const Duration();
204 | }
205 | await channel.invokeMethod('seekTo', {
206 | 'textureId': _textureId,
207 | 'location': moment.inSeconds,
208 | });
209 | value = value.copyWith(position: moment);
210 | }
211 |
212 | //点播为m3u8子流,会自动无缝seek
213 | Future setBitrateIndex(int index) async {
214 | if (_isDisposed) {
215 | return;
216 | }
217 | await channel.invokeMethod('setBitrateIndex', {
218 | 'textureId': _textureId,
219 | 'index': index,
220 | });
221 | value = value.copyWith(bitrateIndex: index);
222 | }
223 |
224 | Future setRate(double rate) async {
225 | if (_isDisposed) {
226 | return;
227 | }
228 | if (rate > 2.0) {
229 | rate = 2.0;
230 | } else if (rate < 1.0) {
231 | rate = 1.0;
232 | }
233 | await channel.invokeMethod('setRate', {
234 | 'textureId': _textureId,
235 | 'rate': rate,
236 | });
237 | value = value.copyWith(rate: rate);
238 | }
239 | }
240 |
241 | ///视频组件生命周期监听
242 | class _VideoAppLifeCycleObserver with WidgetsBindingObserver {
243 | bool _wasPlayingBeforePause = false;
244 | final TencentPlayerController _controller;
245 |
246 | _VideoAppLifeCycleObserver(this._controller);
247 |
248 | void initialize() {
249 | WidgetsBinding.instance!.addObserver(this);
250 | }
251 |
252 | @override
253 | void didChangeAppLifecycleState(AppLifecycleState state) {
254 | switch (state) {
255 | case AppLifecycleState.paused:
256 | _wasPlayingBeforePause = _controller.value.isPlaying;
257 | _controller.pause();
258 | break;
259 | case AppLifecycleState.resumed:
260 | if (_wasPlayingBeforePause) {
261 | _controller.play();
262 | }
263 | break;
264 | default:
265 | }
266 | }
267 |
268 | void dispose() {
269 | WidgetsBinding.instance!.removeObserver(this);
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/lib/flutter_tencentplayer.dart:
--------------------------------------------------------------------------------
1 | export 'model/download_value.dart';
2 | export 'model/player_config.dart';
3 | export 'model/tentcent_player_value.dart';
4 |
5 | export 'controller/download_controller.dart';
6 | export 'controller/tencent_player_controller.dart';
7 |
8 | export 'package:flutter_tencentplayer/view/tencent_player.dart';
9 |
--------------------------------------------------------------------------------
/lib/model/download_value.dart:
--------------------------------------------------------------------------------
1 | class DownloadValue {
2 | final String? downloadStatus;
3 | final int? quanlity;
4 | final int? duration;
5 | final int? size;
6 | final int? downloadSize;
7 | final double? progress;
8 | final String? playPath;
9 | final bool? isStop;
10 | final String? url;
11 | final String? fileId;
12 | final String? error;
13 |
14 | DownloadValue({
15 | this.downloadStatus,
16 | this.quanlity,
17 | this.duration,
18 | this.size,
19 | this.downloadSize,
20 | this.progress,
21 | this.playPath,
22 | this.isStop,
23 | this.url,
24 | this.fileId,
25 | this.error,
26 | });
27 |
28 | DownloadValue copyWith({
29 | String? downloadStatus,
30 | int? quanlity,
31 | int? duration,
32 | int? size,
33 | int? downloadSize,
34 | double? progress,
35 | String? playPath,
36 | bool? isStop,
37 | String? url,
38 | String? fileId,
39 | String? error,
40 | }) {
41 | return DownloadValue(
42 | downloadStatus: downloadStatus ?? this.downloadStatus,
43 | quanlity: quanlity ?? this.quanlity,
44 | duration: duration ?? this.duration,
45 | size: size ?? this.size,
46 | downloadSize: downloadSize ?? this.downloadSize,
47 | progress: progress ?? this.progress,
48 | playPath: playPath ?? this.playPath,
49 | isStop: isStop ?? this.isStop,
50 | url: url ?? this.url,
51 | fileId: fileId ?? this.fileId,
52 | error: error ?? this.error,
53 | );
54 | }
55 |
56 | @override
57 | String toString() {
58 | return toJson().toString();
59 | }
60 |
61 | Map toJson() => {
62 | 'downloadStatus': this.downloadStatus,
63 | 'quanlity': this.quanlity,
64 | 'duration': this.duration,
65 | 'size': this.size,
66 | 'downloadSize': this.downloadSize,
67 | 'progress': this.progress,
68 | 'playPath': this.playPath,
69 | 'isStop': this.isStop,
70 | 'url': this.url,
71 | 'fileId': this.fileId,
72 | 'error': this.error,
73 | };
74 |
75 | factory DownloadValue.fromJson(Map json) {
76 | return DownloadValue(
77 | downloadStatus: json['downloadStatus'] as String?,
78 | quanlity: int.parse((json['quanlity'] ?? 0).toString()),
79 | duration: int.parse(json['duration']),
80 | size: int.parse(json['size']),
81 | downloadSize: int.parse(json['downloadSize']),
82 | progress: double.parse(json['progress']),
83 | playPath: json['playPath'] as String?,
84 | isStop: json['isStop'] == "true",
85 | url: json['url'] as String?,
86 | fileId: json['fileId'] as String?,
87 | error: json['error'] as String?,
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/model/player_config.dart:
--------------------------------------------------------------------------------
1 | class PlayerConfig {
2 | final bool autoPlay;
3 | final bool loop;
4 | final Map? headers;
5 | final String? cachePath;
6 |
7 | // 单位:秒
8 | final double progressInterval;
9 |
10 | // 单位:秒
11 | final int? startTime;
12 | final Map? auth;
13 |
14 | // 后台播放
15 | final bool supportBackground;
16 |
17 | const PlayerConfig({
18 | this.autoPlay = true,
19 | this.loop = false,
20 | this.headers,
21 | this.cachePath,
22 | this.progressInterval = 1,
23 | this.startTime,
24 | this.auth,
25 | this.supportBackground = false,
26 | });
27 |
28 | Map toJson() => {
29 | 'autoPlay': this.autoPlay,
30 | 'loop': this.loop,
31 | 'headers': this.headers,
32 | 'cachePath': this.cachePath,
33 | 'progressInterval': this.progressInterval,
34 | 'startTime': this.startTime,
35 | 'auth': this.auth,
36 | 'supportBackground': this.supportBackground,
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/lib/model/tentcent_player_value.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TencentPlayerValue {
4 | final Duration duration;
5 | final Duration position;
6 | final Duration playable;
7 | final bool isPlaying;
8 | final String? errorDescription;
9 | final Size? size;
10 | final bool isLoading;
11 | final int? netSpeed;
12 | final int? fps;
13 | final double rate;
14 | final int bitrateIndex;
15 | final int orientation;
16 | final int degree;
17 | final int? eventCode;
18 |
19 | bool get initialized => size?.width != null;
20 |
21 | bool get hasError => errorDescription != null;
22 |
23 | double get aspectRatio => size != null
24 | ? size!.width / size!.height > 0.0
25 | ? size!.width / size!.height
26 | : 1.0
27 | : 1.0;
28 |
29 | TencentPlayerValue({
30 | this.duration = const Duration(),
31 | this.position = const Duration(),
32 | this.playable = const Duration(),
33 | this.isPlaying = false,
34 | this.errorDescription,
35 | this.size,
36 | this.isLoading = false,
37 | this.netSpeed,
38 | this.fps = 0,
39 | this.rate = 1.0,
40 | this.bitrateIndex = 0, //TODO 默认清晰度
41 | this.orientation = 0,
42 | this.degree = 0,
43 | this.eventCode,
44 | });
45 |
46 | TencentPlayerValue copyWith({
47 | Duration? duration,
48 | Duration? position,
49 | Duration? playable,
50 | bool? isPlaying,
51 | String? errorDescription,
52 | Size? size,
53 | bool? isLoading,
54 | int? netSpeed,
55 | int? fps,
56 | double? rate,
57 | int? bitrateIndex,
58 | int? orientation,
59 | int? degree,
60 | int? eventCode,
61 | }) {
62 | return TencentPlayerValue(
63 | duration: duration ?? this.duration,
64 | position: position ?? this.position,
65 | playable: playable ?? this.playable,
66 | isPlaying: isPlaying ?? this.isPlaying,
67 | errorDescription: errorDescription ?? this.errorDescription,
68 | size: size ?? this.size,
69 | isLoading: isLoading ?? this.isLoading,
70 | netSpeed: netSpeed ?? this.netSpeed,
71 | rate: rate ?? this.rate,
72 | bitrateIndex: bitrateIndex ?? this.bitrateIndex,
73 | orientation: orientation ?? this.orientation,
74 | degree: degree ?? this.degree,
75 | eventCode: eventCode ?? this.eventCode,
76 | fps: fps ?? this.fps,
77 | );
78 | }
79 |
80 | @override
81 | String toString() {
82 | return '$runtimeType('
83 | 'duration: $duration, '
84 | 'position: $position, '
85 | 'playable: $playable, '
86 | 'isPlaying: $isPlaying, '
87 | 'errorDescription: $errorDescription),'
88 | 'isLoading: $isLoading),'
89 | 'netSpeed: $netSpeed),'
90 | 'fps: $fps),'
91 | 'rate: $rate),'
92 | 'bitrateIndex: $bitrateIndex),'
93 | 'orientation: $orientation),'
94 | 'degree: $degree),'
95 | 'size: $size),'
96 | 'eventCode: $eventCode),';
97 | }
98 | }
99 |
100 | enum DataSourceType { asset, network, file }
101 |
--------------------------------------------------------------------------------
/lib/view/tencent_player.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
5 |
6 | class TencentPlayer extends StatefulWidget {
7 | static MethodChannel channel = const MethodChannel('flutter_tencentplayer')..invokeMethod('init');
8 |
9 | final TencentPlayerController controller;
10 |
11 | TencentPlayer(this.controller);
12 |
13 | @override
14 | _TencentPlayerState createState() => new _TencentPlayerState();
15 | }
16 |
17 | class _TencentPlayerState extends State {
18 | VoidCallback? _listener;
19 | int? _textureId = 0;
20 |
21 | _TencentPlayerState() {
22 | _listener = () {
23 | final int newTextureId = widget.controller.textureId!;
24 | if (newTextureId != _textureId) {
25 | setState(() {
26 | _textureId = newTextureId;
27 | });
28 | }
29 | };
30 | }
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | _textureId = widget.controller.textureId;
36 | widget.controller.addListener(_listener!);
37 | }
38 |
39 | @override
40 | void didUpdateWidget(TencentPlayer oldWidget) {
41 | super.didUpdateWidget(oldWidget);
42 | if (oldWidget.controller.dataSource != widget.controller.dataSource) {
43 | if (Platform.isAndroid) oldWidget.controller.dispose();
44 | }
45 | oldWidget.controller.removeListener(_listener!);
46 | _textureId = widget.controller.textureId;
47 | widget.controller.addListener(_listener!);
48 | }
49 |
50 | @override
51 | void deactivate() {
52 | super.deactivate();
53 | widget.controller.removeListener(_listener!);
54 | }
55 |
56 | @override
57 | Widget build(BuildContext context) {
58 | if (_textureId == null) {
59 | return Container();
60 | } else {
61 | if ((widget.controller.value.degree / 90).floor() == 0) {
62 | return Texture(textureId: _textureId!);
63 | } else {
64 | return RotatedBox(quarterTurns: (widget.controller.value.degree / 90).floor(), child: Texture(textureId: _textureId!));
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.6.1"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.2.0"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | fake_async:
47 | dependency: transitive
48 | description:
49 | name: fake_async
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "1.2.0"
53 | flutter:
54 | dependency: "direct main"
55 | description: flutter
56 | source: sdk
57 | version: "0.0.0"
58 | flutter_test:
59 | dependency: "direct dev"
60 | description: flutter
61 | source: sdk
62 | version: "0.0.0"
63 | matcher:
64 | dependency: transitive
65 | description:
66 | name: matcher
67 | url: "https://pub.flutter-io.cn"
68 | source: hosted
69 | version: "0.12.10"
70 | meta:
71 | dependency: transitive
72 | description:
73 | name: meta
74 | url: "https://pub.flutter-io.cn"
75 | source: hosted
76 | version: "1.3.0"
77 | path:
78 | dependency: transitive
79 | description:
80 | name: path
81 | url: "https://pub.flutter-io.cn"
82 | source: hosted
83 | version: "1.8.0"
84 | sky_engine:
85 | dependency: transitive
86 | description: flutter
87 | source: sdk
88 | version: "0.0.99"
89 | source_span:
90 | dependency: transitive
91 | description:
92 | name: source_span
93 | url: "https://pub.flutter-io.cn"
94 | source: hosted
95 | version: "1.8.1"
96 | stack_trace:
97 | dependency: transitive
98 | description:
99 | name: stack_trace
100 | url: "https://pub.flutter-io.cn"
101 | source: hosted
102 | version: "1.10.0"
103 | stream_channel:
104 | dependency: transitive
105 | description:
106 | name: stream_channel
107 | url: "https://pub.flutter-io.cn"
108 | source: hosted
109 | version: "2.1.0"
110 | string_scanner:
111 | dependency: transitive
112 | description:
113 | name: string_scanner
114 | url: "https://pub.flutter-io.cn"
115 | source: hosted
116 | version: "1.1.0"
117 | term_glyph:
118 | dependency: transitive
119 | description:
120 | name: term_glyph
121 | url: "https://pub.flutter-io.cn"
122 | source: hosted
123 | version: "1.2.0"
124 | test_api:
125 | dependency: transitive
126 | description:
127 | name: test_api
128 | url: "https://pub.flutter-io.cn"
129 | source: hosted
130 | version: "0.3.0"
131 | typed_data:
132 | dependency: transitive
133 | description:
134 | name: typed_data
135 | url: "https://pub.flutter-io.cn"
136 | source: hosted
137 | version: "1.3.0"
138 | vector_math:
139 | dependency: transitive
140 | description:
141 | name: vector_math
142 | url: "https://pub.flutter-io.cn"
143 | source: hosted
144 | version: "2.1.0"
145 | sdks:
146 | dart: ">=2.12.0 <3.0.0"
147 | flutter: ">=1.10.0"
148 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_tencentplayer
2 | description: Is a native plugin for playing Tencent Cloud on-demand videos, live streaming, offline downloads, etc.
3 | version: 2.0.1
4 | author: qq326646683<326646683@qq.com>,yxwandroid
5 | homepage: https://github.com/qq326646683/flutter_tencentplayer
6 |
7 | environment:
8 | sdk: ">=2.12.0 <3.0.0"
9 | flutter: ">=1.10.0 <3.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 |
19 | # For information on the generic Dart part of this file, see the
20 | # following page: https://dart.dev/tools/pub/pubspec
21 |
22 | # The following section is specific to Flutter.
23 | flutter:
24 | # This section identifies this Flutter project as a plugin project.
25 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily
26 | # be modified. They are used by the tooling to maintain consistency when
27 | # adding or updating assets for this project.
28 | plugin:
29 | platforms:
30 | # This plugin project was generated without specifying any
31 | # platforms with the `--platform` argument. If you see the `fake_platform` map below, remove it and
32 | # then add platforms following the instruction here:
33 | # https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms
34 | # -------------------
35 | android:
36 | package: com.jinxian.flutter_tencentplayer
37 | pluginClass: FlutterTencentplayerPlugin
38 | ios:
39 | pluginClass: FlutterTencentplayerPlugin
40 | # -------------------
41 |
42 | # To add assets to your plugin package, add an assets section, like this:
43 | # assets:
44 | # - images/a_dot_burr.jpeg
45 | # - images/a_dot_ham.jpeg
46 | #
47 | # For details regarding assets in packages, see
48 | # https://flutter.dev/assets-and-images/#from-packages
49 | #
50 | # An image asset can refer to one or more resolution-specific "variants", see
51 | # https://flutter.dev/assets-and-images/#resolution-aware.
52 |
53 | # To add custom fonts to your plugin package, add a fonts section here,
54 | # in this "flutter" section. Each entry in this list should have a
55 | # "family" key with the font family name, and a "fonts" key with a
56 | # list giving the asset and other descriptors for the font. For
57 | # example:
58 | # fonts:
59 | # - family: Schyler
60 | # fonts:
61 | # - asset: fonts/Schyler-Regular.ttf
62 | # - asset: fonts/Schyler-Italic.ttf
63 | # style: italic
64 | # - family: Trajan Pro
65 | # fonts:
66 | # - asset: fonts/TrajanPro.ttf
67 | # - asset: fonts/TrajanPro_Bold.ttf
68 | # weight: 700
69 | #
70 | # For details regarding fonts in packages, see
71 | # https://flutter.dev/custom-fonts/#from-packages
72 |
--------------------------------------------------------------------------------
/test/flutter_tencentplayer_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:flutter_tencentplayer/flutter_tencentplayer.dart';
4 |
5 | void main() {
6 | const MethodChannel channel = MethodChannel('flutter_tencentplayer');
7 |
8 | setUp(() {
9 | channel.setMockMethodCallHandler((MethodCall methodCall) async {
10 | return '42';
11 | });
12 | });
13 |
14 | tearDown(() {
15 | channel.setMockMethodCallHandler(null);
16 | });
17 |
18 | test('getPlatformVersion', () async {
19 | // expect(await FlutterTencentplayer.platformVersion, '42');
20 | });
21 | }
22 |
--------------------------------------------------------------------------------