feat(video,android): 视频下载转码播放 + 列表下载/播放按钮
- 集成 ffmpeg-kit-full-gpl 6.0.3(arm64),用 libx264 重编码修复 .h264 缺 SPS/PPS 导致的播放卡死 - 新增 VideoDownloadPlayService/VideoCacheManager,.mp4 直下直播、.h264 下载后转 mp4 缓存,转码完成后删除临时 .h264 - FFmpegKitBridge 走主线程 JNI 调用规避 IL2CPP 后台线程 FindClass 不稳定 - 视频列表按缓存状态显示 下载/播放 按钮,下载中亮色条显示百分比;播放面板原下载按钮改为保存到相册
This commit is contained in:
parent
8074ba71c0
commit
4726107e19
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "Kill.sln",
|
||||
"dotnet.dotnetPath": "C:/Program Files/dotnet/dotnet.exe",
|
||||
"dotrush.roslyn.dotnetSdkDirectory": "C:/Program Files/dotnet/sdk/8.0.113",
|
||||
"dotrush.msbuildProperties": {
|
||||
"DefineConstants": "UNITY_EDITOR"
|
||||
|
||||
40
Assets/Plugins/Android/Exceptions.java
Normal file
40
Assets/Plugins/Android/Exceptions.java
Normal file
@ -0,0 +1,40 @@
|
||||
package com.arthenica.smartexception.java;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* smartexception-java 的最小 stub
|
||||
* ffmpeg-kit 6.0.2 内部 swordfish 库依赖此类的 getStackTraceString / registerRootPackage
|
||||
* 我们不需要完整功能,只需保证类存在且方法签名匹配
|
||||
*/
|
||||
public class Exceptions {
|
||||
|
||||
private static final Set<String> rootPackages = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
public static void registerRootPackage(String rootPackage) {
|
||||
if (rootPackage != null) {
|
||||
rootPackages.add(rootPackage);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getRegisteredRootPackages() {
|
||||
return rootPackages.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static void unregisterRootPackage(String rootPackage) {
|
||||
if (rootPackage != null) {
|
||||
rootPackages.remove(rootPackage);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getStackTraceString(Throwable t) {
|
||||
if (t == null) return "";
|
||||
StringWriter sw = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(sw));
|
||||
return sw.toString();
|
||||
}
|
||||
}
|
||||
32
Assets/Plugins/Android/Exceptions.java.meta
Normal file
32
Assets/Plugins/Android/Exceptions.java.meta
Normal file
@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24b172d3926ad8942a95e2682e783122
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/jniLibs.meta
Normal file
8
Assets/Plugins/Android/jniLibs.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bf7b13fff88f954e8e20570135aa4fa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/jniLibs/arm64-v8a.meta
Normal file
8
Assets/Plugins/Android/jniLibs/arm64-v8a.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df7805ab2d3b7ee4f96106672813ac91
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavcodec.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavcodec.so
Normal file
Binary file not shown.
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavcodec.so.meta
Normal file
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavcodec.so.meta
Normal file
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa7f8d684f49aed42bee73ba831c14e3
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavdevice.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavdevice.so
Normal file
Binary file not shown.
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavdevice.so.meta
Normal file
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavdevice.so.meta
Normal file
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 127b1ca475d6c2c4baeba9d0a82ba22f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavfilter.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavfilter.so
Normal file
Binary file not shown.
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavfilter.so.meta
Normal file
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavfilter.so.meta
Normal file
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 216200a08ba02c84a84097cf5d35beeb
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavformat.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavformat.so
Normal file
Binary file not shown.
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavformat.so.meta
Normal file
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavformat.so.meta
Normal file
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7650b1a65f114ef42bcdbe9917e8184e
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavutil.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libavutil.so
Normal file
Binary file not shown.
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavutil.so.meta
Normal file
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libavutil.so.meta
Normal file
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f3a4d77039e3924b93d7d5f41bea59f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libc++_shared.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libc++_shared.so
Normal file
Binary file not shown.
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dbc173a359c19e4f93988bd069836e5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: false
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libffmpegkit.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libffmpegkit.so
Normal file
Binary file not shown.
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c879c557a252f94c809b6e4e11e1d8f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c79917e539e4a084fbe78be71658249a
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libswresample.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libswresample.so
Normal file
Binary file not shown.
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4366edb3a69081d499c29a18976d20e2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libswscale.so
Normal file
BIN
Assets/Plugins/Android/jniLibs/arm64-v8a/libswscale.so
Normal file
Binary file not shown.
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libswscale.so.meta
Normal file
34
Assets/Plugins/Android/jniLibs/arm64-v8a/libswscale.so.meta
Normal file
@ -0,0 +1,34 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fda05a6417969af418657b2bc27116e8
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
Is16KbAligned: true
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/Android/libs.meta
Normal file
8
Assets/Plugins/Android/libs.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7a31c1ff4b90e64eace12948fd1b59d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Android/libs/ffmpeg-kit-classes.jar
Normal file
BIN
Assets/Plugins/Android/libs/ffmpeg-kit-classes.jar
Normal file
Binary file not shown.
32
Assets/Plugins/Android/libs/ffmpeg-kit-classes.jar.meta
Normal file
32
Assets/Plugins/Android/libs/ffmpeg-kit-classes.jar.meta
Normal file
@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3f31159fe0fd444ca012e3a92066482
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,8 +1,16 @@
|
||||
apply plugin: 'com.android.library'
|
||||
**APPLY_PLUGINS**
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// 提取的 ffmpeg-kit classes.jar(避免 Unity 处理 aar 不解压出 classes 的问题)
|
||||
implementation files('libs/ffmpeg-kit-classes.jar')
|
||||
// jniLibs .so 走 Plugins/Android/jniLibs/ 标准路径,Unity 自动复制到 APK
|
||||
// Android Resolver Dependencies Start
|
||||
implementation 'com.google.android.gms:play-services-base:18.10.0' // Assets/Firebase/Editor/DatabaseDependencies.xml:17
|
||||
// implementation 'com.google.firebase:firebase-analytics:23.0.0' // Assets/Firebase/Editor/AnalyticsDependencies.xml:13
|
||||
|
||||
@ -3,3 +3,9 @@
|
||||
|
||||
# 保留 FilePicker 插件类(通过 AndroidJavaClass 反射调用,R8 无法检测到使用)
|
||||
-keep class com.unity.filepicker.** { *; }
|
||||
|
||||
# 保留 ffmpeg-kit 全部类(R8 混淆后 ffmpeg-kit 内部 native 调用会失效)
|
||||
-keep class com.arthenica.ffmpegkit.** { *; }
|
||||
-keep class com.arthenica.ffmpegkit.**$* { *; }
|
||||
# 保留 smartexception stub(ffmpeg-kit 内部 swordfish 依赖)
|
||||
-keep class com.arthenica.smartexception.java.** { *; }
|
||||
|
||||
@ -1294,6 +1294,11 @@
|
||||
"zh": "下载",
|
||||
"en": "Download"
|
||||
},
|
||||
{
|
||||
"key": "save_to_album",
|
||||
"zh": "保存到相册",
|
||||
"en": "Save to album"
|
||||
},
|
||||
{
|
||||
"key": "100257",
|
||||
"zh": "已保存到相册",
|
||||
|
||||
BIN
Assets/Res/ui/common/texture/白色下载.png
Normal file
BIN
Assets/Res/ui/common/texture/白色下载.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 622 B |
127
Assets/Res/ui/common/texture/白色下载.png.meta
Normal file
127
Assets/Res/ui/common/texture/白色下载.png.meta
Normal file
@ -0,0 +1,127 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c229daec576184744b211d6dba64b86d
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1555,8 +1555,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 33.30693, y: -25}
|
||||
m_SizeDelta: {x: 11.485148, y: 32.158413}
|
||||
m_AnchoredPosition: {x: 34, y: -25}
|
||||
m_SizeDelta: {x: 12, y: 33}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7651326578310032666
|
||||
CanvasRenderer:
|
||||
@ -1634,8 +1634,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 70.0594, y: -25}
|
||||
m_SizeDelta: {x: 62.0198, y: 32.158413}
|
||||
m_AnchoredPosition: {x: 71, y: -25}
|
||||
m_SizeDelta: {x: 62, y: 33}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &5439455909549482936
|
||||
CanvasRenderer:
|
||||
@ -1713,8 +1713,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 13.782178, y: -25}
|
||||
m_SizeDelta: {x: 27.564356, y: 32.158413}
|
||||
m_AnchoredPosition: {x: 14, y: -25}
|
||||
m_SizeDelta: {x: 28, y: 33}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &9138258466124955313
|
||||
CanvasRenderer:
|
||||
@ -2407,7 +2407,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 268.099, y: -45}
|
||||
m_AnchoredPosition: {x: 263.5, y: -45}
|
||||
m_SizeDelta: {x: 48, y: 48}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &1825978237086955098
|
||||
@ -2438,7 +2438,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: d683593328a0cd1439ec851b678d6e53, type: 3}
|
||||
m_Sprite: {fileID: 21300000, guid: 2a30eeb1d11a4c54ab247ce04ff970dc, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
@ -3199,8 +3199,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 385.0099, y: -45}
|
||||
m_SizeDelta: {x: 137.82178, y: 90}
|
||||
m_AnchoredPosition: {x: 381, y: -45}
|
||||
m_SizeDelta: {x: 139, y: 90}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3173438462998470296
|
||||
CanvasRenderer:
|
||||
@ -3256,7 +3256,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 89f1d49b50a31514787b4e268b1c73bb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
textKey: 100256
|
||||
textKey: save_to_album
|
||||
--- !u!1 &9076327582970621402
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -1,5 +1,80 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &497558733087982060
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 5882713478833418639}
|
||||
- component: {fileID: 1036065137287717569}
|
||||
- component: {fileID: 367163259704343791}
|
||||
m_Layer: 5
|
||||
m_Name: Image
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &5882713478833418639
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 497558733087982060}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 9188773615237041945}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: -50, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 50}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &1036065137287717569
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 497558733087982060}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &367163259704343791
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 497558733087982060}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 0}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &877347991771310610
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -79,6 +154,202 @@ MonoBehaviour:
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text:
|
||||
--- !u!1 &2051001936009020673
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4673987278082270048}
|
||||
- component: {fileID: 5362905219182314851}
|
||||
- component: {fileID: 2535855211290697476}
|
||||
m_Layer: 5
|
||||
m_Name: fill
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4673987278082270048
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2051001936009020673}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8623461907935592638}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 56, y: 56}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &5362905219182314851
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2051001936009020673}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &2535855211290697476
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2051001936009020673}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 0c71bc27e9a8003489c4ecac25d77cf7, type: 3}
|
||||
m_Type: 3
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 0
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 2
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &2132022660410915657
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 636836731591056723}
|
||||
- component: {fileID: 3523228025851238942}
|
||||
- component: {fileID: 4899917637513757111}
|
||||
- component: {fileID: 9005445394137887220}
|
||||
m_Layer: 5
|
||||
m_Name: "\u4E0B\u8F7D"
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &636836731591056723
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2132022660410915657}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 9065212358823370560}
|
||||
m_Father: {fileID: 68797095166843205}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 1, y: 0.5}
|
||||
m_AnchorMax: {x: 1, y: 0.5}
|
||||
m_AnchoredPosition: {x: -24, y: 0}
|
||||
m_SizeDelta: {x: 50, y: 50}
|
||||
m_Pivot: {x: 1, y: 0.5}
|
||||
--- !u!222 &3523228025851238942
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2132022660410915657}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &4899917637513757111
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2132022660410915657}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: c229daec576184744b211d6dba64b86d, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &9005445394137887220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2132022660410915657}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 4899917637513757111}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &3598736706289518838
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -91,7 +362,6 @@ GameObject:
|
||||
- component: {fileID: 5299248103163038637}
|
||||
- component: {fileID: 2175641447419279724}
|
||||
- component: {fileID: 9151980594487127953}
|
||||
- component: {fileID: 6414481814838801359}
|
||||
m_Layer: 5
|
||||
m_Name: "\u4E00\u6761\u89C6\u9891"
|
||||
m_TagString: Untagged
|
||||
@ -114,6 +384,8 @@ RectTransform:
|
||||
- {fileID: 3798966534521417101}
|
||||
- {fileID: 7265590296071766734}
|
||||
- {fileID: 9188773615237041945}
|
||||
- {fileID: 636836731591056723}
|
||||
- {fileID: 8623461907935592638}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
@ -174,50 +446,11 @@ MonoBehaviour:
|
||||
timeText: {fileID: 4285570928671144042}
|
||||
videoImage: {fileID: 7879863914677697422}
|
||||
data: {fileID: 4035548829646174203}
|
||||
--- !u!114 &6414481814838801359
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3598736706289518838}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 0
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 2175641447419279724}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
downloadButton: {fileID: 9005445394137887220}
|
||||
playButton: {fileID: 8721096871482888230}
|
||||
loadingImage: {fileID: 293915980322065354}
|
||||
fillLoadingImage: {fileID: 2535855211290697476}
|
||||
videoUrl:
|
||||
--- !u!1 &4411859370997722127
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -229,8 +462,9 @@ GameObject:
|
||||
- component: {fileID: 9188773615237041945}
|
||||
- component: {fileID: 6119825987542687625}
|
||||
- component: {fileID: 7879863914677697422}
|
||||
- component: {fileID: 8721096871482888230}
|
||||
m_Layer: 5
|
||||
m_Name: "\u9884\u89C8\u56FE"
|
||||
m_Name: "\u64AD\u653E"
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@ -247,7 +481,8 @@ RectTransform:
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Children:
|
||||
- {fileID: 5882713478833418639}
|
||||
m_Father: {fileID: 68797095166843205}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 1, y: 0.5}
|
||||
@ -293,6 +528,125 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &8721096871482888230
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4411859370997722127}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 7879863914677697422}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &5010573327037882523
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 9065212358823370560}
|
||||
- component: {fileID: 5849985704768591495}
|
||||
- component: {fileID: 887128703327512524}
|
||||
m_Layer: 5
|
||||
m_Name: Image
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &9065212358823370560
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5010573327037882523}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 636836731591056723}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: -50, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 50}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &5849985704768591495
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5010573327037882523}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &887128703327512524
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5010573327037882523}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 0}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &7520780073196005562
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -372,3 +726,79 @@ MonoBehaviour:
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1.3
|
||||
m_Text:
|
||||
--- !u!1 &8869706884894642105
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8623461907935592638}
|
||||
- component: {fileID: 5473811544253790937}
|
||||
- component: {fileID: 293915980322065354}
|
||||
m_Layer: 5
|
||||
m_Name: "\u8FDB\u5EA6"
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &8623461907935592638
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8869706884894642105}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4673987278082270048}
|
||||
m_Father: {fileID: 68797095166843205}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 1, y: 0.5}
|
||||
m_AnchorMax: {x: 1, y: 0.5}
|
||||
m_AnchoredPosition: {x: -24, y: 0}
|
||||
m_SizeDelta: {x: 56, y: 56}
|
||||
m_Pivot: {x: 1, y: 0.5}
|
||||
--- !u!222 &5473811544253790937
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8869706884894642105}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &293915980322065354
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8869706884894642105}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 85008df8bc29d934bad661ef65c1943c, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 2
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
|
||||
BIN
Assets/Res/ui/videopage/保存到相册.png
Normal file
BIN
Assets/Res/ui/videopage/保存到相册.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
127
Assets/Res/ui/videopage/保存到相册.png.meta
Normal file
127
Assets/Res/ui/videopage/保存到相册.png.meta
Normal file
@ -0,0 +1,127 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a30eeb1d11a4c54ab247ce04ff970dc
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Res/ui/videopage/进度条.png
Normal file
BIN
Assets/Res/ui/videopage/进度条.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
127
Assets/Res/ui/videopage/进度条.png.meta
Normal file
127
Assets/Res/ui/videopage/进度条.png.meta
Normal file
@ -0,0 +1,127 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c71bc27e9a8003489c4ecac25d77cf7
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Res/ui/videopage/进度条底色.png
Normal file
BIN
Assets/Res/ui/videopage/进度条底色.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
127
Assets/Res/ui/videopage/进度条底色.png.meta
Normal file
127
Assets/Res/ui/videopage/进度条底色.png.meta
Normal file
@ -0,0 +1,127 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85008df8bc29d934bad661ef65c1943c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -170,6 +170,7 @@ namespace Kill.UI.Pages
|
||||
{
|
||||
foreach (var item in data.data.records)
|
||||
{
|
||||
Debug.Log(totalCount);
|
||||
|
||||
KillData kd = new KillData()
|
||||
{
|
||||
@ -178,7 +179,7 @@ namespace Kill.UI.Pages
|
||||
dis=item.distance
|
||||
};
|
||||
CreateKillItem(kd);
|
||||
|
||||
Debug.Log(totalCount);
|
||||
totalCount++;
|
||||
|
||||
|
||||
@ -226,8 +227,12 @@ namespace Kill.UI.Pages
|
||||
if (videoUIItemPrefab == null || content == null) return;
|
||||
|
||||
var item = Instantiate(videoUIItemPrefab, content);
|
||||
item.GetComponent<VideoUIItem>().Init(data.time, data.coverUrl);
|
||||
item.GetComponent<Button>().onClick.AddListener(() => ClickOneVideo(data.videoUrl));
|
||||
var comp = item.GetComponent<VideoUIItem>();
|
||||
comp.Init(data.time, data.coverUrl);
|
||||
comp.SetupVideo(data.videoUrl,
|
||||
onDownload: () => OnItemDownload(comp),
|
||||
onPlay: () => OnItemPlay(comp));
|
||||
//item.GetComponent<Button>().onClick.AddListener(() => OnItemRowClick(comp));
|
||||
videoList.Add(item.gameObject);
|
||||
}
|
||||
/// <summary>
|
||||
@ -243,37 +248,86 @@ namespace Kill.UI.Pages
|
||||
videoList.Add(item.gameObject);
|
||||
}
|
||||
public VideoCtrl videoCtrl;
|
||||
public void ClickOneVideo(string url)
|
||||
public Button downloadBtn;
|
||||
|
||||
/// <summary>用本地 mp4 打开播放器面板,并把"保存到相册"按钮绑定到该本地文件</summary>
|
||||
private void OpenPlayer(string path)
|
||||
{
|
||||
if (videoCtrl == null) { ToastUI.Show("100332"); return; }
|
||||
videoCtrl.transform.parent.gameObject.SetActive(true);
|
||||
videoCtrl.Init(url,null);
|
||||
downloadBtn.onClick.RemoveAllListeners();
|
||||
downloadBtn.onClick.AddListener(() => ClickDownload(url));
|
||||
videoCtrl.Init(path, null);
|
||||
if (downloadBtn != null)
|
||||
{
|
||||
downloadBtn.onClick.RemoveAllListeners();
|
||||
downloadBtn.onClick.AddListener(() => SaveVideoToGallery(path, Path.GetFileName(path)));
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseVideo()
|
||||
{
|
||||
videoCtrl.transform.parent.gameObject.SetActive(false);
|
||||
videoCtrl.Stop();
|
||||
}
|
||||
public async Task DownLoadVideo(string url)
|
||||
|
||||
// ============ 列表项:下载 / 播放 逻辑 ============
|
||||
|
||||
/// <summary>点 item 下载按钮:仅下载(+h264 转码),完成后刷新按钮为"播放"</summary>
|
||||
public void OnItemDownload(VideoUIItem item)
|
||||
{
|
||||
UpdateLoadingUI.Instance.Show();
|
||||
string name = url.Substring(url.LastIndexOf('/') + 1);
|
||||
string savePath = Application.persistentDataPath + "/"+name;
|
||||
var success = await NetworkCtrl.Instance.DownloadFile(url, savePath, UpdateLoadingUI.Instance.GetDownloadProcess);
|
||||
UpdateLoadingUI.Instance.Hide();
|
||||
|
||||
if (success.IsSuccess && File.Exists(savePath))
|
||||
{
|
||||
// 下载成功,保存到系统相册
|
||||
SaveVideoToGallery(savePath, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToastUI.Show("100258");
|
||||
}
|
||||
if (item == null || string.IsNullOrEmpty(item.videoUrl)) return;
|
||||
item.ShowDownloading(true);
|
||||
new Kill.Video.VideoDownloadPlayService().DownloadConvertAndPlay(
|
||||
item.videoUrl,
|
||||
onDownloadProgress: p => item.UpdateDownloadProgress(false, p),
|
||||
onConvertStart: null,
|
||||
onComplete: (success, path) =>
|
||||
{
|
||||
item.UpdateDownloadProgress(true, null);
|
||||
if (success) item.RefreshButtons();
|
||||
else ToastUI.Show("100258");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>点 item 播放按钮:播本地缓存;未缓存则先下载+转码再播</summary>
|
||||
public void OnItemPlay(VideoUIItem item)
|
||||
{
|
||||
if (item == null || string.IsNullOrEmpty(item.videoUrl)) return;
|
||||
string path = Kill.Video.VideoCacheManager.GetCachedMp4Path(item.videoUrl);
|
||||
if (File.Exists(path) && new FileInfo(path).Length > 0)
|
||||
OpenPlayer(path);
|
||||
else
|
||||
OnItemDownloadAndPlay(item);
|
||||
}
|
||||
|
||||
/// <summary>点击 item 整行:已下载直接播,未下载先下载(+转码)再播</summary>
|
||||
public void OnItemRowClick(VideoUIItem item)
|
||||
{
|
||||
if (item == null || string.IsNullOrEmpty(item.videoUrl)) return;
|
||||
if (Kill.Video.VideoCacheManager.IsCached(item.videoUrl))
|
||||
OpenPlayer(Kill.Video.VideoCacheManager.GetCachedMp4Path(item.videoUrl));
|
||||
else
|
||||
OnItemDownloadAndPlay(item);
|
||||
}
|
||||
|
||||
private void OnItemDownloadAndPlay(VideoUIItem item)
|
||||
{
|
||||
if (item == null || string.IsNullOrEmpty(item.videoUrl)) return;
|
||||
item.ShowDownloading(true);
|
||||
new Kill.Video.VideoDownloadPlayService().DownloadConvertAndPlay(
|
||||
item.videoUrl,
|
||||
onDownloadProgress: p => item.UpdateDownloadProgress(false, p),
|
||||
onConvertStart: null,
|
||||
onComplete: (success, path) =>
|
||||
{
|
||||
item.UpdateDownloadProgress(true, null);
|
||||
item.RefreshButtons();
|
||||
if (success && !string.IsNullOrEmpty(path) && File.Exists(path))
|
||||
OpenPlayer(path);
|
||||
else
|
||||
ToastUI.Show("100258");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存视频到系统相册
|
||||
/// </summary>
|
||||
@ -295,15 +349,6 @@ namespace Kill.UI.Pages
|
||||
});
|
||||
#endif
|
||||
}
|
||||
public Button downloadBtn;
|
||||
public void ClickDownload(string url)
|
||||
{
|
||||
DownLoadVideo(url);
|
||||
}
|
||||
public void GetDownloadProcess( DownloadProgress progress)
|
||||
{
|
||||
Debug.Log(progress.ProgressPercentage + "%");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -12,13 +12,50 @@ namespace Kill.UI.Pages
|
||||
public Text timeText;
|
||||
public Image videoImage;
|
||||
public Text data;
|
||||
/// <summary>视频项:未下载时显示下载按钮(prefab 内挂载)</summary>
|
||||
public Button downloadButton;
|
||||
/// <summary>视频项:已下载时显示播放按钮(prefab 内挂载)</summary>
|
||||
public Button playButton;
|
||||
/// <summary>下载进度条底色(显示/隐藏用)</summary>
|
||||
public Image loadingImage;
|
||||
/// <summary>亮色进度条(loadingImage 的子物体,Image type=Filled,进度填充到它身上)</summary>
|
||||
public Image fillLoadingImage;
|
||||
public string videoUrl;
|
||||
bool isVideo = false;
|
||||
|
||||
private Image FillBar => fillLoadingImage != null ? fillLoadingImage : loadingImage;
|
||||
|
||||
/// <summary>显示/隐藏下载进度条,开始时归零;下载中隐藏 下载/播放 按钮,完成后由 RefreshButtons 恢复</summary>
|
||||
public void ShowDownloading(bool show)
|
||||
{
|
||||
if (loadingImage != null) loadingImage.gameObject.SetActive(show);
|
||||
var bar = FillBar;
|
||||
if (show && bar != null) bar.fillAmount = 0f;
|
||||
// 下载期间收起 下载/播放 按钮,只留进度条
|
||||
if (downloadButton != null) downloadButton.gameObject.SetActive(false);
|
||||
if (playButton != null) playButton.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>用下载进度刷新亮色条 fillAmount(0~1),并在完成时隐藏底色</summary>
|
||||
public void UpdateDownloadProgress(bool done, Kill.Network.DownloadProgress progress)
|
||||
{
|
||||
if (done)
|
||||
{
|
||||
if (loadingImage != null) loadingImage.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
var bar = FillBar;
|
||||
if (bar == null) return;
|
||||
float pct = progress != null ? progress.ProgressPercentage : 0f;
|
||||
bar.fillAmount = Mathf.Clamp01(pct / 100f);
|
||||
}
|
||||
|
||||
public void Init(DateTime time,string path="",float angle=0,float dis=0)
|
||||
{
|
||||
timeText.text = time.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
if(path!="")
|
||||
{
|
||||
videoImage.gameObject.SetActive(true);
|
||||
GetComponent<Button>().interactable=true;
|
||||
//NetworkCtrl.Instance.LoadImageToUIImageAsync(path, videoImage);
|
||||
data.text="";
|
||||
}
|
||||
@ -36,10 +73,49 @@ namespace Kill.UI.Pages
|
||||
disStr=lensFt+"ft";
|
||||
}
|
||||
videoImage.gameObject.SetActive(false);
|
||||
GetComponent<Button>().interactable=false;
|
||||
data.text=$"{angle}° {disStr}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视频项专用:绑定 url 与 下载/播放 两个按钮的回调
|
||||
/// </summary>
|
||||
/// <param name="url">视频源 url(.mp4 或 .h264)</param>
|
||||
/// <param name="onDownload">点下载按钮</param>
|
||||
/// <param name="onPlay">点播放按钮</param>
|
||||
public void SetupVideo(string url, UnityEngine.Events.UnityAction onDownload, UnityEngine.Events.UnityAction onPlay)
|
||||
{
|
||||
videoUrl = url;
|
||||
isVideo = true;
|
||||
if (downloadButton != null)
|
||||
{
|
||||
downloadButton.onClick.RemoveAllListeners();
|
||||
downloadButton.onClick.AddListener(onDownload);
|
||||
}
|
||||
if (playButton != null)
|
||||
{
|
||||
playButton.onClick.RemoveAllListeners();
|
||||
playButton.onClick.AddListener(onPlay);
|
||||
}
|
||||
RefreshButtons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据下载状态刷新按钮显示:未下载显下载、已下载显播放;非视频项隐藏两个按钮
|
||||
/// </summary>
|
||||
public void RefreshButtons()
|
||||
{
|
||||
if (downloadButton == null || playButton == null) return;
|
||||
if (!isVideo)
|
||||
{
|
||||
downloadButton.gameObject.SetActive(false);
|
||||
playButton.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
bool cached = Kill.Video.VideoCacheManager.IsCached(videoUrl);
|
||||
downloadButton.gameObject.SetActive(!cached);
|
||||
playButton.gameObject.SetActive(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8
Assets/Scripts/Video.meta
Normal file
8
Assets/Scripts/Video.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e42067f07639a5443b94ceff03670e62
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
263
Assets/Scripts/Video/FFmpegKitBridge.cs
Normal file
263
Assets/Scripts/Video/FFmpegKitBridge.cs
Normal file
@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Kill.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Kill.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// ffmpeg-kit-maintained(维护版,群组 dev.ffmpegkit-maintained)C# 桥接
|
||||
/// 用 AndroidJNI 底层 API 直接调,绕开 AndroidJavaClass.CallStatic 的 Unity bug(Ljava.lang.Object)
|
||||
/// .264 -> .mp4 用 libx264 重编码(full-gpl 版),补齐干净的 SPS/PPS + 时间戳,
|
||||
/// 供各手机(含 OPPO/MTK)解码器正常播放;纯 copy 会复制坏帧导致播放卡死。
|
||||
/// 注意:所有 JNI 调用必须在 Unity 主线程执行(IL2CPP 工作线程 JVM attach 不可靠,FindClass
|
||||
/// 偶发失败);现依赖为 ffmpeg-kit-full-gpl(含 libx264),free 版没有该编码器。
|
||||
/// </summary>
|
||||
public static class FFmpegKitBridge
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 异步 .264 -> .mp4 纯封装
|
||||
/// </summary>
|
||||
public static Task<bool> ConvertH264ToMp4Async(string inputPath, string outputPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputPath) || !File.Exists(inputPath))
|
||||
return Task.FromResult(false);
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
try { File.Delete(outputPath); } catch { }
|
||||
}
|
||||
|
||||
// 设备产出的 .264 裸流 SPS/PPS 引用损坏,纯 -c:v copy 会复制坏帧导致
|
||||
// 播放器解码卡死("non-existing PPS 0 referenced")。必须是重编码(libx264)
|
||||
// 重新生成干净的 SPS/PPS + 时间戳,等价 service 端 VideoTranscodeService。
|
||||
// 注意:需要 ffmpeg-kit-full(含 libx264);free 版无 libx264 编码器会失败。
|
||||
// preset ultrafast 更快速;crf 值控制质量,视频短可接受
|
||||
string[] args = new string[] {
|
||||
"-y",
|
||||
"-loglevel", "quiet",
|
||||
"-fflags", "+genpts",
|
||||
"-i", inputPath,
|
||||
"-c:v", "libx264",
|
||||
"-preset", "ultrafast",
|
||||
"-crf", "23",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-movflags", "+faststart",
|
||||
outputPath
|
||||
};
|
||||
Debug.Log("[FFmpegKitBridge] args: " + string.Join(" ", args));
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
bool result = false;
|
||||
// JNI 必须在主线程执行,否则 FindClass 偶发失败("类未找到")
|
||||
MainThreadDispatcher.RunOnMainThread(() => result = RunFfmpegConvert(args, outputPath));
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>在 Unity 主线程执行 ffmpeg JNI 调用(同步,阻塞)</summary>
|
||||
private static bool RunFfmpegConvert(string[] args, string outputPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var ffkitClass = new AndroidJavaClass("com.arthenica.ffmpegkit.FFmpegKit"))
|
||||
{
|
||||
IntPtr classPtr = ffkitClass.GetRawClass();
|
||||
if (classPtr == IntPtr.Zero)
|
||||
{
|
||||
Debug.LogError("[FFmpegKitBridge] FFmpegKit 类未找到(类不在 classpath,多半是装了旧 APK)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 主动 FindClass 二重确认类存在(区分"类缺失" vs "方法签名不匹配")
|
||||
IntPtr findCls = AndroidJNI.FindClass("com/arthenica/ffmpegkit/FFmpegKit");
|
||||
Debug.Log("[FFmpegKitBridge] FindClass => " + (findCls == IntPtr.Zero ? "NOT_FOUND" : "OK"));
|
||||
|
||||
// ffmpeg-kit 6.0+ 用 executeWithArguments(String[]) 而不是 execute(String)
|
||||
IntPtr executeMethodID = AndroidJNI.GetStaticMethodID(
|
||||
classPtr,
|
||||
"executeWithArguments",
|
||||
"([Ljava/lang/String;)Lcom/arthenica/ffmpegkit/FFmpegSession;");
|
||||
|
||||
if (executeMethodID == IntPtr.Zero)
|
||||
{
|
||||
Debug.LogError("[FFmpegKitBridge] executeMethodID == 0:类存在但方法 executeWithArguments 未找到(签名不匹配)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构造 String[] 作为 JNI 参数
|
||||
IntPtr argsArr = AndroidJNI.NewObjectArray(args.Length,
|
||||
AndroidJNI.FindClass("java/lang/String"), IntPtr.Zero);
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
IntPtr strPtr = AndroidJNI.NewStringUTF(args[i]);
|
||||
AndroidJNI.SetObjectArrayElement(argsArr, i, strPtr);
|
||||
AndroidJNI.DeleteLocalRef(strPtr);
|
||||
}
|
||||
|
||||
jvalue[] jargs = new jvalue[1];
|
||||
jargs[0].l = argsArr;
|
||||
|
||||
IntPtr sessionPtr = AndroidJNI.CallStaticObjectMethod(
|
||||
classPtr, executeMethodID, jargs);
|
||||
AndroidJNI.DeleteLocalRef(argsArr);
|
||||
|
||||
if (sessionPtr == IntPtr.Zero)
|
||||
{
|
||||
Debug.LogError("[FFmpegKitBridge] execute return null");
|
||||
return false;
|
||||
}
|
||||
|
||||
try { return ReadSessionResult(sessionPtr, outputPath); }
|
||||
finally { AndroidJNI.DeleteLocalRef(sessionPtr); }
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("[FFmpegKitBridge] execute fail: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>读取 FFmpegSession 结果并解析成功与否</summary>
|
||||
private static bool ReadSessionResult(IntPtr sessionPtr, string outputPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读 ReturnCode
|
||||
IntPtr getReturnCodeMID = AndroidJNI.GetMethodID(
|
||||
AndroidJNI.GetObjectClass(sessionPtr),
|
||||
"getReturnCode", "()Lcom/arthenica/ffmpegkit/ReturnCode;");
|
||||
IntPtr rcPtr = AndroidJNI.CallObjectMethod(sessionPtr, getReturnCodeMID, new jvalue[0]);
|
||||
if (rcPtr == IntPtr.Zero)
|
||||
{
|
||||
Debug.LogError("[FFmpegKitBridge] returnCode null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ReturnCode.getValue() int
|
||||
IntPtr rcClassPtr = AndroidJNI.GetObjectClass(rcPtr);
|
||||
IntPtr getValueMID = AndroidJNI.GetMethodID(rcClassPtr, "getValue", "()I");
|
||||
int rcValue = AndroidJNI.CallIntMethod(rcPtr, getValueMID, new jvalue[0]);
|
||||
|
||||
// ReturnCode.isValueSuccess() boolean
|
||||
IntPtr isSuccessMID = AndroidJNI.GetMethodID(rcClassPtr, "isValueSuccess", "()Z");
|
||||
bool isSuccess = AndroidJNI.CallBooleanMethod(rcPtr, isSuccessMID, new jvalue[0]);
|
||||
|
||||
Debug.Log("[FFmpegKitBridge] rc=" + rcValue + " isSuccess=" + isSuccess);
|
||||
|
||||
// ffmpeg-kit 6.0.2 在某些场景下 rc=255 (SIGURG) 但 ffmpeg 已写完 mp4
|
||||
// 兜底:同时检查输出文件存在 + 大小 > 0
|
||||
if (!isSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
var fi = new FileInfo(outputPath);
|
||||
if (fi.Length > 0)
|
||||
{
|
||||
Debug.LogWarning("[FFmpegKitBridge] ffmpeg exit signal 但 mp4 已生成 (" + fi.Length + " bytes),按成功处理");
|
||||
isSuccess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// 失败时打印 ffmpeg 错误日志
|
||||
if (!isSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr sessionClass = AndroidJNI.GetObjectClass(sessionPtr);
|
||||
IntPtr failStackMID = AndroidJNI.GetMethodID(sessionClass, "getFailStackTrace", "()Ljava/lang/String;");
|
||||
string failStack = AndroidJNI.CallStringMethod(sessionPtr, failStackMID, new jvalue[0]);
|
||||
if (!string.IsNullOrEmpty(failStack))
|
||||
Debug.LogError("[FFmpegKitBridge] failStack: " + failStack);
|
||||
}
|
||||
catch { }
|
||||
try
|
||||
{
|
||||
IntPtr sessionClass = AndroidJNI.GetObjectClass(sessionPtr);
|
||||
IntPtr logsMID = AndroidJNI.GetMethodID(sessionClass, "getAllLogsAsString", "()Ljava/lang/String;");
|
||||
string logs = AndroidJNI.CallStringMethod(sessionPtr, logsMID, new jvalue[0]);
|
||||
if (!string.IsNullOrEmpty(logs))
|
||||
Debug.LogError("[FFmpegKitBridge] allLogs: " + logs);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
AndroidJNI.DeleteLocalRef(rcPtr);
|
||||
return isSuccess;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("[FFmpegKitBridge] read result fail: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#region debug-point vf-1 (调试图: 读取生成 mp4 的时长/帧数/profile/level,用于区分"截断文件 / 时间戳错误 / 解码不支持")
|
||||
/// <summary>
|
||||
/// 用 ffprobe 读取 mp4 的媒体信息并以 JSON 原文打日志(证据收集,临时插桩)
|
||||
/// </summary>
|
||||
public static void ProbeMp4Info(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path)) return;
|
||||
MainThreadDispatcher.RunOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] args = new string[]
|
||||
{
|
||||
"-v", "error",
|
||||
"-show_entries", "format=duration,size,bit_rate:stream=codec_name,profile,level,width,height,r_frame_rate,avg_frame_rate,nb_frames,time_base,start_time",
|
||||
"-of", "json", path
|
||||
};
|
||||
using (var probeClass = new AndroidJavaClass("com.arthenica.ffmpegkit.FFprobeKit"))
|
||||
{
|
||||
IntPtr classPtr = probeClass.GetRawClass();
|
||||
IntPtr mID = AndroidJNI.GetStaticMethodID(
|
||||
classPtr,
|
||||
"executeWithArguments",
|
||||
"([Ljava/lang/String;)Lcom/arthenica/ffmpegkit/FFprobeSession;");
|
||||
if (mID == IntPtr.Zero) { Debug.LogError("[ProbeMp4Info] no executeWithArguments"); return; }
|
||||
|
||||
IntPtr argsArr = AndroidJNI.NewObjectArray(args.Length,
|
||||
AndroidJNI.FindClass("java/lang/String"), IntPtr.Zero);
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
IntPtr s = AndroidJNI.NewStringUTF(args[i]);
|
||||
AndroidJNI.SetObjectArrayElement(argsArr, i, s);
|
||||
AndroidJNI.DeleteLocalRef(s);
|
||||
}
|
||||
jvalue[] ja = new jvalue[1]; ja[0].l = argsArr;
|
||||
IntPtr sessionPtr = AndroidJNI.CallStaticObjectMethod(classPtr, mID, ja);
|
||||
AndroidJNI.DeleteLocalRef(argsArr);
|
||||
if (sessionPtr == IntPtr.Zero) { Debug.LogError("[ProbeMp4Info] session null"); return; }
|
||||
try
|
||||
{
|
||||
IntPtr cls = AndroidJNI.GetObjectClass(sessionPtr);
|
||||
IntPtr getOutMID = AndroidJNI.GetMethodID(cls, "getOutput", "()Ljava/lang/String;");
|
||||
string outJson = AndroidJNI.CallStringMethod(sessionPtr, getOutMID, new jvalue[0]);
|
||||
Debug.Log("[ProbeMp4Info] ffprobe json: " + (outJson ?? "null"));
|
||||
}
|
||||
finally { AndroidJNI.DeleteLocalRef(sessionPtr); }
|
||||
}
|
||||
}
|
||||
catch (Exception e) { Debug.LogError("[ProbeMp4Info] fail: " + e); }
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#else
|
||||
public static Task<bool> ConvertH264ToMp4Async(string inputPath, string outputPath)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Video/FFmpegKitBridge.cs.meta
Normal file
11
Assets/Scripts/Video/FFmpegKitBridge.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 889748d8e0ed12948bb5bca9d84174c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Assets/Scripts/Video/MainThreadDispatcher.cs
Normal file
67
Assets/Scripts/Video/MainThreadDispatcher.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Kill.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// 把任意 Action 排到 Unity 主线程执行。
|
||||
/// 背景:IL2CPP 的自建后台线程 JVM attach 不可靠,AndroidJavaClass/AndroidJNI
|
||||
/// 在这些线程上 FindClass 会偶发返回空,导致"类未找到/偶尔成功偶尔失败"。
|
||||
/// 用它在主线程上跑所有 JNI 调用即可稳定。
|
||||
/// </summary>
|
||||
public static class MainThreadDispatcher
|
||||
{
|
||||
private static readonly ConcurrentQueue<Action> _pending = new ConcurrentQueue<Action>();
|
||||
private static int _mainThreadId = -1;
|
||||
private static bool _ensured;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
private static void Ensure()
|
||||
{
|
||||
if (_ensured) return;
|
||||
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
var go = new GameObject("MainThreadDispatcher");
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||
go.AddComponent<DispatcherBehaviour>();
|
||||
_ensured = true;
|
||||
}
|
||||
|
||||
public static bool IsMainThread =>
|
||||
!_ensured || Thread.CurrentThread.ManagedThreadId == _mainThreadId;
|
||||
|
||||
/// <summary>在主线程执行 action;若调用方已是主线程则直接内联执行。</summary>
|
||||
public static void RunOnMainThread(Action action)
|
||||
{
|
||||
if (action == null) return;
|
||||
if (IsMainThread)
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
using (var done = new ManualResetEventSlim(false))
|
||||
{
|
||||
_pending.Enqueue(() =>
|
||||
{
|
||||
try { action(); }
|
||||
finally { done.Set(); }
|
||||
});
|
||||
done.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private class DispatcherBehaviour : MonoBehaviour
|
||||
{
|
||||
private void Update()
|
||||
{
|
||||
while (_pending.TryDequeue(out var a))
|
||||
{
|
||||
try { a(); }
|
||||
catch (Exception e) { Debug.LogException(e); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Video/MainThreadDispatcher.cs.meta
Normal file
11
Assets/Scripts/Video/MainThreadDispatcher.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0156e0cc83e452440aea1ea2ceb09be4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
64
Assets/Scripts/Video/VideoCacheManager.cs
Normal file
64
Assets/Scripts/Video/VideoCacheManager.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Kill.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// 视频缓存管理:按 url 哈希缓存到 persistentDataPath/video_cache
|
||||
/// key=mp4(转码后),key=tmp.h264(下载中临时)
|
||||
/// </summary>
|
||||
public static class VideoCacheManager
|
||||
{
|
||||
private const string CacheSubDir = "video_cache";
|
||||
|
||||
private static string CacheDir
|
||||
{
|
||||
get
|
||||
{
|
||||
string dir = Path.Combine(Application.persistentDataPath, CacheSubDir);
|
||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>已转码好的 mp4 缓存路径</summary>
|
||||
public static string GetCachedMp4Path(string url)
|
||||
{
|
||||
return Path.Combine(CacheDir, Hash(url) + ".mp4");
|
||||
}
|
||||
|
||||
/// <summary>下载中的 .h264 临时路径</summary>
|
||||
public static string GetTempH264Path(string url)
|
||||
{
|
||||
return Path.Combine(CacheDir, Hash(url) + ".h264");
|
||||
}
|
||||
|
||||
public static bool IsCached(string url)
|
||||
{
|
||||
var p = GetCachedMp4Path(url);
|
||||
return File.Exists(p) && new FileInfo(p).Length > 0;
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
string dir = Path.Combine(Application.persistentDataPath, CacheSubDir);
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
try { Directory.Delete(dir, true); }
|
||||
catch (Exception e) { Debug.LogWarning("[VideoCacheManager] clear fail: " + e.Message); }
|
||||
}
|
||||
}
|
||||
|
||||
private static string Hash(string s)
|
||||
{
|
||||
using var sha = SHA1.Create();
|
||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(s));
|
||||
var sb = new StringBuilder(bytes.Length * 2);
|
||||
foreach (var b in bytes) sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Video/VideoCacheManager.cs.meta
Normal file
11
Assets/Scripts/Video/VideoCacheManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2e512329ac2d2140807f2f1535d9ef1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
152
Assets/Scripts/Video/VideoDownloadPlayService.cs
Normal file
152
Assets/Scripts/Video/VideoDownloadPlayService.cs
Normal file
@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Kill.Network;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Kill.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载 .264 -> 转封装 MP4(Android MediaCodec+MediaMuxer) -> 返回本地 mp4 路径
|
||||
/// 不直接控制 VideoPlayer,由调用方拿到路径后自行 Init
|
||||
/// </summary>
|
||||
public class VideoDownloadPlayService
|
||||
{
|
||||
/// <summary>
|
||||
/// 完整流程:下载 → 转码
|
||||
/// </summary>
|
||||
/// <param name="url">视频源 url(.264 裸流)</param>
|
||||
/// <param name="onDownloadProgress">下载进度回调(0~1)</param>
|
||||
/// <param name="onConvertStart">下载完成,即将开始转码(可更新 UI 提示)</param>
|
||||
/// <param name="onComplete">(success, mp4Path) success=false 时 mp4Path 为 null/错误信息</param>
|
||||
public async Task<bool> DownloadConvertAndPlay(
|
||||
string url,
|
||||
Action<DownloadProgress> onDownloadProgress,
|
||||
Action<string> onConvertStart,
|
||||
Action<bool, string> onComplete)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
onComplete?.Invoke(false, "url is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
string mp4Path = VideoCacheManager.GetCachedMp4Path(url);
|
||||
|
||||
// 1) 已缓存(mp4 直接缓存 / h264 转码后缓存),直接返回
|
||||
if (File.Exists(mp4Path) && new FileInfo(mp4Path).Length > 0)
|
||||
{
|
||||
onComplete?.Invoke(true, mp4Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2a) 链接是 .mp4:直接下载即得,无需转码
|
||||
if (!IsH264Url(url))
|
||||
{
|
||||
SafeDelete(mp4Path);
|
||||
HttpResponse<string> respMp4 = null;
|
||||
try
|
||||
{
|
||||
respMp4 = await NetworkCtrl.Instance.DownloadFile(url, mp4Path, onDownloadProgress);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("[VideoDownloadPlayService] mp4 download exception: " + e);
|
||||
SafeDelete(mp4Path);
|
||||
onComplete?.Invoke(false, e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (respMp4 == null || !respMp4.IsSuccess || !File.Exists(mp4Path) || new FileInfo(mp4Path).Length <= 0)
|
||||
{
|
||||
SafeDelete(mp4Path);
|
||||
onComplete?.Invoke(false, "mp4 download fail: " + (respMp4?.ErrorMessage ?? "unknown"));
|
||||
return false;
|
||||
}
|
||||
|
||||
onComplete?.Invoke(true, mp4Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2b) 链接是 .h264:先下载再转码
|
||||
string h264Path = VideoCacheManager.GetTempH264Path(url);
|
||||
SafeDelete(h264Path);
|
||||
SafeDelete(mp4Path);
|
||||
|
||||
HttpResponse<string> resp = null;
|
||||
try
|
||||
{
|
||||
resp = await NetworkCtrl.Instance.DownloadFile(url, h264Path, onDownloadProgress);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("[VideoDownloadPlayService] download exception: " + e);
|
||||
SafeDelete(h264Path);
|
||||
onComplete?.Invoke(false, e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resp == null || !resp.IsSuccess || !File.Exists(h264Path))
|
||||
{
|
||||
SafeDelete(h264Path);
|
||||
onComplete?.Invoke(false, "download fail: " + (resp?.ErrorMessage ?? "unknown"));
|
||||
return false;
|
||||
}
|
||||
|
||||
onConvertStart?.Invoke(h264Path);
|
||||
|
||||
// 3) 转码 .264 -> .mp4(ffmpeg-kit,libx264 重编码)
|
||||
bool convertOk = await FFmpegKitBridge.ConvertH264ToMp4Async(h264Path, mp4Path);
|
||||
if (!convertOk)
|
||||
{
|
||||
Debug.LogError("[VideoDownloadPlayService] ffmpeg convert fail");
|
||||
}
|
||||
|
||||
#region debug-point vf-2 (调试图: 记录源/产物文件大小 + 生成的 mp4 媒体信息)
|
||||
try
|
||||
{
|
||||
var hi = new FileInfo(h264Path);
|
||||
var mi = new FileInfo(mp4Path);
|
||||
Debug.Log("[vf-2] h264Size=" + (hi.Exists ? hi.Length.ToString() : "missing")
|
||||
+ " mp4Size=" + (mi.Exists ? mi.Length.ToString() : "missing")
|
||||
+ " convertOk=" + convertOk);
|
||||
if (mi.Exists && mi.Length > 0)
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
FFmpegKitBridge.ProbeMp4Info(mp4Path);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) { Debug.LogError("[vf-2] " + e); }
|
||||
#endregion
|
||||
|
||||
// 转码完成,删除 .h264 临时文件
|
||||
SafeDelete(h264Path);
|
||||
// 失败时清理未完成 mp4
|
||||
if (!convertOk || !File.Exists(mp4Path))
|
||||
{
|
||||
SafeDelete(mp4Path);
|
||||
onComplete?.Invoke(false, "convert fail");
|
||||
return false;
|
||||
}
|
||||
|
||||
onComplete?.Invoke(true, mp4Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void SafeDelete(string p)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p)) return;
|
||||
try { if (File.Exists(p)) File.Delete(p); } catch { }
|
||||
}
|
||||
|
||||
/// <summary>判断链接是否为 .h264/.264 裸流(mp4 等其他一律按可直接下载处理)</summary>
|
||||
public static bool IsH264Url(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url)) return false;
|
||||
var l = url.ToLowerInvariant();
|
||||
return l.EndsWith(".h264") || l.EndsWith(".264", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Video/VideoDownloadPlayService.cs.meta
Normal file
11
Assets/Scripts/Video/VideoDownloadPlayService.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8c906cd03e5e1a439ced63198d73476
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
FFmpegKitFull/README.md
Normal file
53
FFmpegKitFull/README.md
Normal file
@ -0,0 +1,53 @@
|
||||
# FFmpegKit (full-gpl) 归档与放置说明
|
||||
|
||||
本项目用 ffmpeg-kit 在 Android 端做 H.264/裸流 → MP4 转码(libx264 重编码),
|
||||
以修复各设备(如 OPPO/MTK)解码器对坏 PPS 流播放卡死的问题。
|
||||
|
||||
## 依赖来源
|
||||
|
||||
- Maven: `dev.ffmpegkit-maintained:ffmpeg-kit-full-gpl:6.0.3`
|
||||
(原 `com.arthenica:ffmpeg-kit-full` 已于 2025-04 从 Maven Central 下架;`full` 不含 x264,必须用 `full-gpl`)
|
||||
- FFmpeg 内部版本: **n6.1.6**
|
||||
- 含 libx264(重编码用)
|
||||
- 仅 arm64-v8a(项目只发布 arm64)
|
||||
|
||||
本目录 `ffmpeg-kit-full-gpl-6.0.3.aar` 即下载的原始包(36.6MB),作为来源/备份。
|
||||
|
||||
## 运行时文件位置(勿乱动!)
|
||||
|
||||
Unity Android 对 native 库与 jar 的打包位置有强约束,放错地方会导致 APK 缺少
|
||||
native 库或类,运行时 `UnsatisfiedLinkError`/`ClassNotFoundException`。请**保持现状**:
|
||||
|
||||
- `.so` → `Assets/Plugins/Android/jniLibs/arm64-v8a/`
|
||||
- libavcodec / libavdevice / libavfilter / libavformat / libavutil
|
||||
- libswresample / libswscale
|
||||
- libffmpegkit / libffmpegkit_abidetect
|
||||
- **libc++_shared.so(必带,full 构建的 C++ 运行时依赖)**
|
||||
- 类包 → `Assets/Plugins/Android/libs/ffmpeg-kit-classes.jar`
|
||||
(由 `Assets/Plugins/Android/mainTemplate.gradle` 的
|
||||
`implementation files('libs/ffmpeg-kit-classes.jar')` 引用)
|
||||
|
||||
## 调用链
|
||||
|
||||
- C# 桥接: `Assets/Scripts/Video/FFmpegKitBridge.cs`
|
||||
- `#if UNITY_ANDROID && !UNITY_EDITOR`(Android 专用)
|
||||
- JNI 必须在 Unity 主线程执行:经由 `MainThreadDispatcher.cs` 派发
|
||||
- 转码参数: `-c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p -movflags +faststart -fflags +genpts`
|
||||
- 视频页: `Assets/Scripts/UI/Pages/VideoPage/VideoPageCtrl.cs`
|
||||
- 下载/播放服务: `Assets/Scripts/Video/VideoDownloadPlayService.cs`
|
||||
|
||||
## 升级方法
|
||||
|
||||
1. 重新下载目标版本 aar。
|
||||
2. 解压,用 `jni/arm64-v8a/*.so` 整体覆盖上面 jniLibs 目录(含 libc++_shared.so)。
|
||||
3. 用 aar 内 `classes.jar` 覆盖 libs/ 下的 jar。
|
||||
4. clean 重建 release(不要增量)。
|
||||
|
||||
## 备注
|
||||
|
||||
- x264 为 GPL,与服务端已用的 libx264 转码策略一致,License 口径以项目自身为准。
|
||||
- 播放验证关键日志:
|
||||
- `[VideoDownloadPlayService]` -> `[FFmpegKitBridge] args`(确认含 `libx264`)
|
||||
- `rc=` `isSuccess=True`、`[ProbeMp4Info]` mp4 正常尺寸
|
||||
- `[vf-3]` VideoPrepared 的 frameCount / length 正常
|
||||
- 播放过程不应再出现长时间卡帧。
|
||||
BIN
FFmpegKitFull/ffmpeg-kit-full-gpl-6.0.3.aar
Normal file
BIN
FFmpegKitFull/ffmpeg-kit-full-gpl-6.0.3.aar
Normal file
Binary file not shown.
@ -18,7 +18,7 @@
|
||||
"depth": 0,
|
||||
"source": "git",
|
||||
"dependencies": {},
|
||||
"hash": "ad2a81ec3068eafd48753df1825a8dd7201ce60c"
|
||||
"hash": "c7923746fa5e95916bf1a4087eceadb72305d5b5"
|
||||
},
|
||||
"com.unity.ext.nunit": {
|
||||
"version": "1.0.6",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user