fix:增加ios解码实现
This commit is contained in:
parent
ca95779043
commit
e375c0aeb4
@ -1,4 +1,4 @@
|
|||||||
org.gradle.jvmargs=-Xmx4G
|
org.gradle.jvmargs=-Xmx4G
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
org.gradle.java.home=C:/Users/liyi/other/jdk-17.0.1
|
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.0.1.jdk/Contents/Home
|
||||||
|
|||||||
41
example/ios/Podfile.lock
Normal file
41
example/ios/Podfile.lock
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
PODS:
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- integration_test (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- permission_handler_apple (9.1.1):
|
||||||
|
- Flutter
|
||||||
|
- video_decode_plugin (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
- video_decode_plugin (from `.symlinks/plugins/video_decode_plugin/ios`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
integration_test:
|
||||||
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
|
permission_handler_apple:
|
||||||
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
video_decode_plugin:
|
||||||
|
:path: ".symlinks/plugins/video_decode_plugin/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
|
integration_test: 2d03ab552da9a1f408709a6acf3d7ca4cb3cb307
|
||||||
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
|
permission_handler_apple: 3787117e48f80715ff04a3830ca039283d6a4f29
|
||||||
|
video_decode_plugin: 07649b4703fdf618daf7000af58f3b251c3e280f
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
@ -10,7 +10,9 @@
|
|||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
3CE7980F7865F7F8E5B7162F /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 521BE986C2B423CB297B1C00 /* Pods_RunnerTests.framework */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
8D7AB43641B575B850A72098 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F27CD0209D3FA843C2B3E9E /* Pods_Runner.framework */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
@ -42,9 +44,15 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
1F27CD0209D3FA843C2B3E9E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
21DF05E93201EF3DED427347 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
4A2F7419BFF633E97CA4B2ED /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
521BE986C2B423CB297B1C00 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
6A7EFB3AABB3AB94044A2F39 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
6D4BFC721107664848B40160 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
@ -55,13 +63,24 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
A46450F4F351DEEECD7F5A62 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
A84C216D2CDBF62BB951B792 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
2AE63D1C6EB40BB145F7AFAE /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
3CE7980F7865F7F8E5B7162F /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
8D7AB43641B575B850A72098 /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -94,6 +113,8 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
A8D9A7D8E0142B6FF06E6590 /* Pods */,
|
||||||
|
ACF8A28F7E0BB4CDED852419 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@ -121,6 +142,28 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A8D9A7D8E0142B6FF06E6590 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
21DF05E93201EF3DED427347 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
6A7EFB3AABB3AB94044A2F39 /* Pods-Runner.release.xcconfig */,
|
||||||
|
A84C216D2CDBF62BB951B792 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
4A2F7419BFF633E97CA4B2ED /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
A46450F4F351DEEECD7F5A62 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
6D4BFC721107664848B40160 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
ACF8A28F7E0BB4CDED852419 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1F27CD0209D3FA843C2B3E9E /* Pods_Runner.framework */,
|
||||||
|
521BE986C2B423CB297B1C00 /* Pods_RunnerTests.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -128,8 +171,10 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
4E3E18A6B5EBAEE36E80FB44 /* [CP] Check Pods Manifest.lock */,
|
||||||
331C807D294A63A400263BE5 /* Sources */,
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
331C807F294A63A400263BE5 /* Resources */,
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
2AE63D1C6EB40BB145F7AFAE /* Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -145,12 +190,14 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
ACFD06CDD74A2D9EC586D2CF /* [CP] Check Pods Manifest.lock */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
C1DA649D0A31D2DCC95D2BAA /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -238,6 +285,28 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
};
|
};
|
||||||
|
4E3E18A6B5EBAEE36E80FB44 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@ -253,6 +322,45 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
ACFD06CDD74A2D9EC586D2CF /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
C1DA649D0A31D2DCC95D2BAA /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@ -361,16 +469,20 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = U3J8U8WN26;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NAQ5PL2DYC;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = top.skychip.videoDecodePluginExample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.starlock.lock.local;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Debug_com.starlock.lock.local.mobileprovision;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
@ -379,6 +491,7 @@
|
|||||||
};
|
};
|
||||||
331C8088294A63A400263BE5 /* Debug */ = {
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4A2F7419BFF633E97CA4B2ED /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@ -396,6 +509,7 @@
|
|||||||
};
|
};
|
||||||
331C8089294A63A400263BE5 /* Release */ = {
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = A46450F4F351DEEECD7F5A62 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@ -411,6 +525,7 @@
|
|||||||
};
|
};
|
||||||
331C808A294A63A400263BE5 /* Profile */ = {
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 6D4BFC721107664848B40160 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@ -541,16 +656,20 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = U3J8U8WN26;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NAQ5PL2DYC;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = top.skychip.videoDecodePluginExample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.starlock.lock.local;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Debug_com.starlock.lock.local.mobileprovision;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -564,16 +683,20 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = U3J8U8WN26;
|
DEVELOPMENT_TEAM = "";
|
||||||
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NAQ5PL2DYC;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = top.skychip.videoDecodePluginExample;
|
PRODUCT_BUNDLE_IDENTIFIER = com.starlock.lock.local;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Debug_com.starlock.lock.local.mobileprovision;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
|||||||
@ -4,4 +4,7 @@
|
|||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@ -24,6 +26,8 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@ -41,9 +45,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -281,7 +281,7 @@ class _VideoViewState extends State<VideoView> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_h264Frames = frames;
|
_h264Frames = frames;
|
||||||
});
|
});
|
||||||
_log("H264文件解析完成,找到 "+frames.length.toString()+" 个帧");
|
_log("H264文件解析完成,找到 " + frames.length.toString() + " 个帧");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找起始码的辅助方法
|
// 查找起始码的辅助方法
|
||||||
@ -303,7 +303,6 @@ class _VideoViewState extends State<VideoView> {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取NAL类型
|
|
||||||
// 获取NAL类型
|
// 获取NAL类型
|
||||||
int _getNalType(Uint8List data) {
|
int _getNalType(Uint8List data) {
|
||||||
// 跳过起始码后再获取NAL单元类型
|
// 跳过起始码后再获取NAL单元类型
|
||||||
@ -389,6 +388,36 @@ class _VideoViewState extends State<VideoView> {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final timestamp = DateTime.now().microsecondsSinceEpoch;
|
final timestamp = DateTime.now().microsecondsSinceEpoch;
|
||||||
|
// 如果是I帧,先发送SPS和PPS
|
||||||
|
int nalType = _getNalType(frame.data);
|
||||||
|
if (nalType == NalUnitType.CODED_SLICE_IDR) {
|
||||||
|
// 查找最近的SPS和PPS
|
||||||
|
H264Frame? sps, pps;
|
||||||
|
for (int i = frameSeq - 1; i >= 0; i--) {
|
||||||
|
int t = _getNalType(_h264Frames[i].data);
|
||||||
|
if (sps == null && t == NalUnitType.SPS) sps = _h264Frames[i];
|
||||||
|
if (pps == null && t == NalUnitType.PPS) pps = _h264Frames[i];
|
||||||
|
if (sps != null && pps != null) break;
|
||||||
|
}
|
||||||
|
if (sps != null) {
|
||||||
|
await VideoDecodePlugin.sendFrame(
|
||||||
|
frameData: sps.data,
|
||||||
|
frameType: 0,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq - 2,
|
||||||
|
splitNalFromIFrame: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pps != null) {
|
||||||
|
await VideoDecodePlugin.sendFrame(
|
||||||
|
frameData: pps.data,
|
||||||
|
frameType: 0,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq - 1,
|
||||||
|
splitNalFromIFrame: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
await VideoDecodePlugin.sendFrame(
|
await VideoDecodePlugin.sendFrame(
|
||||||
frameData: frame.data,
|
frameData: frame.data,
|
||||||
frameType: frame.frameType,
|
frameType: frame.frameType,
|
||||||
@ -506,20 +535,11 @@ class _VideoViewState extends State<VideoView> {
|
|||||||
if (_currentFrameIndex >= _h264Frames.length) {
|
if (_currentFrameIndex >= _h264Frames.length) {
|
||||||
_log("所有帧已解码,重新开始");
|
_log("所有帧已解码,重新开始");
|
||||||
_currentFrameIndex = 0;
|
_currentFrameIndex = 0;
|
||||||
|
|
||||||
// 重新发送SPS和PPS
|
|
||||||
await _sendSpsAndPps();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final frame = _h264Frames[_currentFrameIndex];
|
final frame = _h264Frames[_currentFrameIndex];
|
||||||
|
// 未初始化成功时不发送解码帧
|
||||||
|
if (!_isInitialized || _textureId == null) return;
|
||||||
await _decodeNextFrame(frame, _currentFrameIndex);
|
await _decodeNextFrame(frame, _currentFrameIndex);
|
||||||
|
|
||||||
// 只有在成功解码的情况下才显示日志信息
|
|
||||||
// if (!decodeSuccess && _enablePacketLoss) {
|
|
||||||
// _log("跳过索引 $_currentFrameIndex 的帧(丢帧模拟)");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 无论解码是否成功,都移动到下一帧
|
|
||||||
_currentFrameIndex++;
|
_currentFrameIndex++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -837,7 +857,6 @@ class _VideoViewState extends State<VideoView> {
|
|||||||
},
|
},
|
||||||
child: Text('刷新'),
|
child: Text('刷新'),
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -6,7 +6,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
@ -14,7 +14,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
characters:
|
characters:
|
||||||
@ -22,7 +22,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
clock:
|
clock:
|
||||||
@ -30,7 +30,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
collection:
|
collection:
|
||||||
@ -38,7 +38,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.18.0"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
@ -46,7 +46,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
fake_async:
|
fake_async:
|
||||||
@ -54,7 +54,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
ffi:
|
ffi:
|
||||||
@ -62,7 +62,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
file:
|
file:
|
||||||
@ -70,7 +70,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
flutter:
|
flutter:
|
||||||
@ -88,7 +88,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493
|
sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -111,7 +111,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.0"
|
version: "10.0.0"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
@ -119,7 +119,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
@ -127,7 +127,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
lints:
|
lints:
|
||||||
@ -135,7 +135,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c
|
sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
matcher:
|
matcher:
|
||||||
@ -143,7 +143,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16+1"
|
version: "0.12.16+1"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
@ -151,7 +151,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.8.0"
|
||||||
meta:
|
meta:
|
||||||
@ -159,7 +159,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.0"
|
||||||
path:
|
path:
|
||||||
@ -167,7 +167,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
@ -175,7 +175,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
@ -183,7 +183,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.2.4"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@ -191,7 +191,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
@ -199,7 +199,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
@ -207,7 +207,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
@ -215,7 +215,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
permission_handler:
|
permission_handler:
|
||||||
@ -223,7 +223,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
|
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.4.5"
|
version: "10.4.5"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
@ -231,7 +231,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
|
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.6"
|
version: "10.3.6"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
@ -239,7 +239,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.4"
|
version: "9.1.4"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
@ -247,7 +247,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.12.0"
|
version: "3.12.0"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
@ -255,7 +255,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: permission_handler_windows
|
name: permission_handler_windows
|
||||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.1.3"
|
||||||
platform:
|
platform:
|
||||||
@ -263,7 +263,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
@ -271,7 +271,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
process:
|
process:
|
||||||
@ -279,7 +279,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: process
|
name: process
|
||||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.2"
|
version: "5.0.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
@ -292,7 +292,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
@ -300,7 +300,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
@ -308,7 +308,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
@ -316,7 +316,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
sync_http:
|
sync_http:
|
||||||
@ -324,7 +324,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: sync_http
|
name: sync_http
|
||||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
@ -332,7 +332,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
test_api:
|
test_api:
|
||||||
@ -340,7 +340,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.6.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
@ -348,7 +348,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
video_decode_plugin:
|
video_decode_plugin:
|
||||||
@ -363,7 +363,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.0.0"
|
version: "13.0.0"
|
||||||
webdriver:
|
webdriver:
|
||||||
@ -371,7 +371,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: webdriver
|
name: webdriver
|
||||||
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
@ -379,7 +379,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
|
|||||||
@ -1,19 +1,209 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
public class VideoDecodePlugin: NSObject, FlutterPlugin {
|
public class VideoDecodePlugin: NSObject, FlutterPlugin, FlutterTexture {
|
||||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
private var channel: FlutterMethodChannel?
|
||||||
let channel = FlutterMethodChannel(name: "video_decode_plugin", binaryMessenger: registrar.messenger())
|
private var registrar: FlutterPluginRegistrar?
|
||||||
let instance = VideoDecodePlugin()
|
private var decoder: VideoDecoder?
|
||||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
private var textureId: Int64?
|
||||||
}
|
private var textureRegistry: FlutterTextureRegistry?
|
||||||
|
private var latestPixelBuffer: CVPixelBuffer?
|
||||||
|
private let textureQueue = DispatchQueue(label: "video_decode_plugin.texture.queue")
|
||||||
|
private var cachedSps: Data?
|
||||||
|
private var cachedPps: Data?
|
||||||
|
private var hasNotifiedFlutter = false
|
||||||
|
|
||||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
switch call.method {
|
let channel = FlutterMethodChannel(name: "video_decode_plugin", binaryMessenger: registrar.messenger())
|
||||||
case "getPlatformVersion":
|
let instance = VideoDecodePlugin()
|
||||||
result("iOS " + UIDevice.current.systemVersion)
|
instance.channel = channel
|
||||||
default:
|
instance.registrar = registrar
|
||||||
result(FlutterMethodNotImplemented)
|
instance.textureRegistry = registrar.textures()
|
||||||
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
switch call.method {
|
||||||
|
case "initDecoder":
|
||||||
|
handleInitDecoder(call: call, result: result)
|
||||||
|
case "decodeFrame":
|
||||||
|
handleDecodeFrame(call: call, result: result)
|
||||||
|
case "releaseDecoder":
|
||||||
|
handleReleaseDecoder(call: call, result: result)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 初始化解码器
|
||||||
|
private func handleInitDecoder(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
guard let args = call.arguments as? [String: Any],
|
||||||
|
let width = args["width"] as? Int,
|
||||||
|
let height = args["height"] as? Int,
|
||||||
|
let codecType = args["codecType"] as? String else {
|
||||||
|
result(FlutterError(code: "INVALID_ARGS", message: "参数错误", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 释放旧解码器和纹理
|
||||||
|
decoder?.release()
|
||||||
|
decoder = nil
|
||||||
|
if let tid = textureId {
|
||||||
|
textureRegistry?.unregisterTexture(tid)
|
||||||
|
textureId = nil
|
||||||
|
}
|
||||||
|
// 注册Flutter纹理
|
||||||
|
guard let registry = textureRegistry else {
|
||||||
|
result(FlutterError(code: "NO_TEXTURE_REGISTRY", message: "无法获取纹理注册表", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let textureId = registry.register(self)
|
||||||
|
self.textureId = textureId
|
||||||
|
// 创建解码器
|
||||||
|
let decoder = VideoDecoder(width: width, height: height, codecType: codecType)
|
||||||
|
self.decoder = decoder
|
||||||
|
decoder.onFrameDecoded = { [weak self] pixelBuffer, _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.textureQueue.async {
|
||||||
|
self.latestPixelBuffer = pixelBuffer
|
||||||
|
self.textureRegistry?.textureFrameAvailable(self.textureId ?? 0)
|
||||||
|
if !self.hasNotifiedFlutter {
|
||||||
|
self.hasNotifiedFlutter = true
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.channel?.invokeMethod("onFrameRendered", arguments: ["textureId": self.textureId ?? 0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("[VideoDecodePlugin] 解码器初始化成功,textureId=\(textureId)")
|
||||||
|
result(textureId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 新增:去除NALU起始码的工具方法(增强日志与健壮性)
|
||||||
|
private func stripStartCode(_ data: Data) -> Data {
|
||||||
|
let originalLen = data.count
|
||||||
|
let naluType: UInt8 = {
|
||||||
|
if data.count > 4 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x01 {
|
||||||
|
return data[4] & 0x1F
|
||||||
|
} else if data.count > 3 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01 {
|
||||||
|
return data[3] & 0x1F
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}()
|
||||||
|
var stripped: Data = data
|
||||||
|
if data.count > 4 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x01 {
|
||||||
|
stripped = data.subdata(in: 4..<data.count)
|
||||||
|
} else if data.count > 3 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01 {
|
||||||
|
stripped = data.subdata(in: 3..<data.count)
|
||||||
|
}
|
||||||
|
let strippedLen = stripped.count
|
||||||
|
let strippedType: UInt8 = stripped.count > 0 ? (stripped[0] & 0x1F) : 0
|
||||||
|
if strippedLen < 3 || (strippedType != 7 && strippedType != 8) {
|
||||||
|
print("[VideoDecodePlugin][警告] strip后NALU长度或类型异常,type=", strippedType, "len=", strippedLen)
|
||||||
|
}
|
||||||
|
// 只在异常时输出警告,不再输出详细内容
|
||||||
|
return stripped
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解码视频帧
|
||||||
|
private func handleDecodeFrame(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
guard let args = call.arguments as? [String: Any],
|
||||||
|
let frameData = args["frameData"] as? FlutterStandardTypedData,
|
||||||
|
let frameType = args["frameType"] as? Int,
|
||||||
|
let timestamp = args["timestamp"] as? Int,
|
||||||
|
let frameSeq = args["frameSeq"] as? Int else {
|
||||||
|
result(FlutterError(code: "INVALID_ARGS", message: "参数错误", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let refIFrameSeq = args["refIFrameSeq"] as? Int
|
||||||
|
let data = frameData.data
|
||||||
|
|
||||||
|
// 解析NALU类型
|
||||||
|
let naluType: UInt8 = {
|
||||||
|
if data.count > 4 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x01 {
|
||||||
|
return data[4] & 0x1F
|
||||||
|
} else if data.count > 3 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01 {
|
||||||
|
return data[3] & 0x1F
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}()
|
||||||
|
|
||||||
|
print("[VideoDecodePlugin][调试] handleDecodeFrame: frameType=\(frameType), naluType=\(naluType), cachedSpsLen=\(cachedSps?.count ?? 0), cachedPpsLen=\(cachedPps?.count ?? 0)")
|
||||||
|
|
||||||
|
// 缓存SPS/PPS(去除起始码)并立即尝试初始化解码器
|
||||||
|
if naluType == 7 { // SPS
|
||||||
|
// 只缓存,不再输出详细日志
|
||||||
|
cachedSps = stripStartCode(data)
|
||||||
|
result(true)
|
||||||
|
return
|
||||||
|
} else if naluType == 8 { // PPS
|
||||||
|
// 只缓存,不再输出详细日志
|
||||||
|
cachedPps = stripStartCode(data)
|
||||||
|
result(true)
|
||||||
|
return
|
||||||
|
} else if naluType == 5 { // IDR/I帧
|
||||||
|
// 先提取第一个合法NALU,直接推送
|
||||||
|
let firstNalu = extractFirstValidNalu(data)
|
||||||
|
if firstNalu.isEmpty { return }
|
||||||
|
print("[VideoDecodePlugin] 发送I帧, 长度: \(firstNalu.count), 头部: \(firstNalu.prefix(8).map { String(format: "%02X", $0) }.joined(separator: " ")), cachedSps长度: \(cachedSps?.count ?? 0), cachedPps长度: \(cachedPps?.count ?? 0)")
|
||||||
|
decoder?.decodeFrame(frameData: firstNalu, frameType: frameType, timestamp: Int64(timestamp), frameSeq: frameSeq, refIFrameSeq: refIFrameSeq, sps: cachedSps, pps: cachedPps)
|
||||||
|
} else {
|
||||||
|
// 先提取第一个合法NALU,直接推送
|
||||||
|
let firstNalu = extractFirstValidNalu(data)
|
||||||
|
if firstNalu.isEmpty { return }
|
||||||
|
print("[VideoDecodePlugin] 发送P/B帧, 长度: \(firstNalu.count), 头部: \(firstNalu.prefix(8).map { String(format: "%02X", $0) }.joined(separator: " "))")
|
||||||
|
decoder?.decodeFrame(frameData: firstNalu, frameType: frameType, timestamp: Int64(timestamp), frameSeq: frameSeq, refIFrameSeq: refIFrameSeq)
|
||||||
|
}
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 释放解码器资源
|
||||||
|
private func handleReleaseDecoder(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
decoder?.release()
|
||||||
|
decoder = nil
|
||||||
|
if let tid = textureId {
|
||||||
|
textureRegistry?.unregisterTexture(tid)
|
||||||
|
textureId = nil
|
||||||
|
}
|
||||||
|
latestPixelBuffer = nil
|
||||||
|
hasNotifiedFlutter = false
|
||||||
|
print("[VideoDecodePlugin] 解码器和纹理已释放")
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - FlutterTexture协议实现
|
||||||
|
public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
|
||||||
|
var pixelBuffer: CVPixelBuffer?
|
||||||
|
textureQueue.sync {
|
||||||
|
pixelBuffer = self.latestPixelBuffer
|
||||||
|
}
|
||||||
|
if let pb = pixelBuffer {
|
||||||
|
return Unmanaged.passRetained(pb)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增side_data检测工具
|
||||||
|
private func checkNaluForSideData(_ nalu: Data, naluType: UInt8) -> Bool {
|
||||||
|
if (naluType == 5 && nalu.count > 10000) || (naluType != 7 && naluType != 8 && nalu.count > 10000) {
|
||||||
|
print("[VideoDecodePlugin][警告] NALU长度异常,可能包含side_data,type=\(naluType),len=\(nalu.count)")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:提取第一个合法NALU工具
|
||||||
|
private func extractFirstValidNalu(_ nalu: Data) -> Data {
|
||||||
|
guard let start = nalu.range(of: Data([0x00, 0x00, 0x00, 0x01]))?.lowerBound else {
|
||||||
|
print("[VideoDecodePlugin][警告] NALU无AnnexB起始码,丢弃该帧")
|
||||||
|
return Data()
|
||||||
|
}
|
||||||
|
let searchRange = (start+4)..<nalu.count
|
||||||
|
if let next = nalu[searchRange].range(of: Data([0x00, 0x00, 0x00, 0x01]))?.lowerBound {
|
||||||
|
let end = searchRange.lowerBound + next
|
||||||
|
return nalu[start..<end]
|
||||||
|
} else {
|
||||||
|
return nalu[start..<nalu.count]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
334
ios/Classes/VideoDecoder.swift
Normal file
334
ios/Classes/VideoDecoder.swift
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
import Foundation
|
||||||
|
import VideoToolbox
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
/// 视频解码器,基于VideoToolbox实现H264/H265硬件解码,输出CVPixelBuffer
|
||||||
|
class VideoDecoder {
|
||||||
|
enum CodecType: String {
|
||||||
|
case h264 = "h264"
|
||||||
|
case h265 = "h265"
|
||||||
|
var codecType: CMVideoCodecType {
|
||||||
|
switch self {
|
||||||
|
case .h264: return kCMVideoCodecType_H264
|
||||||
|
case .h265: return kCMVideoCodecType_HEVC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var decompressionSession: VTDecompressionSession?
|
||||||
|
private var formatDesc: CMVideoFormatDescription?
|
||||||
|
private let width: Int
|
||||||
|
private let height: Int
|
||||||
|
private let codecType: CodecType
|
||||||
|
private let decodeQueue = DispatchQueue(label: "video_decode_plugin.decode.queue")
|
||||||
|
private var isSessionReady = false
|
||||||
|
private var lastIFrameSeq: Int?
|
||||||
|
private var frameSeqSet = Set<Int>()
|
||||||
|
private let maxAllowedDelayMs: Int = 350
|
||||||
|
private var timestampBaseMs: Int64?
|
||||||
|
private var firstFrameRelativeTimestamp: Int64?
|
||||||
|
|
||||||
|
// ===== 新增:缓冲区与自适应帧率相关成员 =====
|
||||||
|
private let inputQueue = DispatchQueue(label: "video_decode_plugin.input.queue", attributes: .concurrent)
|
||||||
|
private var inputBuffer: [(frameData: Data, frameType: Int, timestamp: Int64, frameSeq: Int, refIFrameSeq: Int?, sps: Data?, pps: Data?)] = []
|
||||||
|
private let inputBufferSemaphore = DispatchSemaphore(value: 1)
|
||||||
|
private let inputBufferMaxCount = 30
|
||||||
|
|
||||||
|
private let outputQueue = DispatchQueue(label: "video_decode_plugin.output.queue", attributes: .concurrent)
|
||||||
|
private var outputBuffer: [(pixelBuffer: CVPixelBuffer, timestamp: Int64)] = []
|
||||||
|
private let outputBufferSemaphore = DispatchSemaphore(value: 1)
|
||||||
|
private let outputBufferMaxCount = 20
|
||||||
|
|
||||||
|
private var renderThread: Thread?
|
||||||
|
private var renderThreadRunning = false
|
||||||
|
private var hasNotifiedFlutter = false
|
||||||
|
|
||||||
|
// 帧率自适应参数
|
||||||
|
private var renderFps: Int = 15
|
||||||
|
private var smoothedFps: Double = 15.0
|
||||||
|
private let alpha: Double = 0.2
|
||||||
|
private let minFps: Double = 8.0
|
||||||
|
private let maxFps: Double = 30.0
|
||||||
|
private let maxStep: Double = 2.0
|
||||||
|
private var renderedTimestamps: [Int64] = [] // ms
|
||||||
|
private let renderedTimestampsMaxCount = 20
|
||||||
|
private var renderedFrameCount = 0
|
||||||
|
private let fpsAdjustInterval = 10
|
||||||
|
|
||||||
|
/// 解码回调
|
||||||
|
var onFrameDecoded: ((CVPixelBuffer, Int64) -> Void)? = { _, _ in }
|
||||||
|
|
||||||
|
/// 初始化解码器
|
||||||
|
init(width: Int, height: Int, codecType: String) {
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.codecType = CodecType(rawValue: codecType.lowercased()) ?? .h264
|
||||||
|
startRenderThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 新增:输入缓冲区入队方法 =====
|
||||||
|
private func enqueueInput(_ item: (Data, Int, Int64, Int, Int?, Data?, Data?)) {
|
||||||
|
inputQueue.async(flags: .barrier) {
|
||||||
|
if self.inputBuffer.count >= self.inputBufferMaxCount {
|
||||||
|
self.inputBuffer.removeFirst()
|
||||||
|
}
|
||||||
|
self.inputBuffer.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ===== 新增:输入缓冲区出队方法 =====
|
||||||
|
private func dequeueInput() -> (Data, Int, Int64, Int, Int?, Data?, Data?)? {
|
||||||
|
var item: (Data, Int, Int64, Int, Int?, Data?, Data?)?
|
||||||
|
inputQueue.sync {
|
||||||
|
if !self.inputBuffer.isEmpty {
|
||||||
|
item = self.inputBuffer.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
// ===== 新增:输出缓冲区入队方法 =====
|
||||||
|
private func enqueueOutput(_ item: (CVPixelBuffer, Int64)) {
|
||||||
|
outputQueue.async(flags: .barrier) {
|
||||||
|
if self.outputBuffer.count >= self.outputBufferMaxCount {
|
||||||
|
self.outputBuffer.removeFirst()
|
||||||
|
}
|
||||||
|
self.outputBuffer.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ===== 新增:输出缓冲区出队方法 =====
|
||||||
|
private func dequeueOutput() -> (CVPixelBuffer, Int64)? {
|
||||||
|
var item: (CVPixelBuffer, Int64)?
|
||||||
|
outputQueue.sync {
|
||||||
|
if !self.outputBuffer.isEmpty {
|
||||||
|
item = self.outputBuffer.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
// ===== 新增:渲染线程启动与停止 =====
|
||||||
|
private func startRenderThread() {
|
||||||
|
renderThreadRunning = true
|
||||||
|
renderThread = Thread { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
while self.renderThreadRunning {
|
||||||
|
let frameIntervalMs = Int(1000.0 / self.smoothedFps)
|
||||||
|
let loopStart = Date().timeIntervalSince1970 * 1000.0
|
||||||
|
if let (pixelBuffer, timestamp) = self.dequeueOutput() {
|
||||||
|
// 渲染到Flutter纹理
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.onFrameDecoded?(pixelBuffer, timestamp)
|
||||||
|
}
|
||||||
|
// 只在首次渲染时回调Flutter
|
||||||
|
if !self.hasNotifiedFlutter {
|
||||||
|
self.hasNotifiedFlutter = true
|
||||||
|
// 由外部插件层负责onFrameRendered回调
|
||||||
|
}
|
||||||
|
// 帧率统计
|
||||||
|
self.renderedTimestamps.append(Int64(Date().timeIntervalSince1970 * 1000))
|
||||||
|
if self.renderedTimestamps.count > self.renderedTimestampsMaxCount {
|
||||||
|
self.renderedTimestamps.removeFirst()
|
||||||
|
}
|
||||||
|
self.renderedFrameCount += 1
|
||||||
|
if self.renderedFrameCount % self.fpsAdjustInterval == 0 {
|
||||||
|
let measuredFps = self.calculateDecodeFps()
|
||||||
|
let newFps = self.updateSmoothedFps(measuredFps)
|
||||||
|
self.renderFps = newFps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 控制渲染节奏
|
||||||
|
let loopCost = Int(Date().timeIntervalSince1970 * 1000.0 - loopStart)
|
||||||
|
let sleepMs = frameIntervalMs - loopCost
|
||||||
|
if sleepMs > 0 {
|
||||||
|
Thread.sleep(forTimeInterval: Double(sleepMs) / 1000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderThread?.start()
|
||||||
|
}
|
||||||
|
private func stopRenderThread() {
|
||||||
|
renderThreadRunning = false
|
||||||
|
renderThread?.cancel()
|
||||||
|
renderThread = nil
|
||||||
|
}
|
||||||
|
// ===== 新增:帧率统计与EMA算法 =====
|
||||||
|
private func calculateDecodeFps() -> Double {
|
||||||
|
guard renderedTimestamps.count >= 2 else { return smoothedFps }
|
||||||
|
let first = renderedTimestamps.first!
|
||||||
|
let last = renderedTimestamps.last!
|
||||||
|
let frameCount = renderedTimestamps.count - 1
|
||||||
|
let durationMs = max(last - first, 1)
|
||||||
|
return Double(frameCount) * 1000.0 / Double(durationMs)
|
||||||
|
}
|
||||||
|
private func updateSmoothedFps(_ measuredFps: Double) -> Int {
|
||||||
|
let safeFps = min(max(measuredFps, minFps), maxFps)
|
||||||
|
let targetFps = alpha * safeFps + (1 - alpha) * smoothedFps
|
||||||
|
let delta = targetFps - smoothedFps
|
||||||
|
let step = min(max(delta, -maxStep), maxStep)
|
||||||
|
smoothedFps = min(max(smoothedFps + step, minFps), maxFps)
|
||||||
|
return Int(smoothedFps)
|
||||||
|
}
|
||||||
|
/// 初始化解码会话(首次收到I帧时调用)
|
||||||
|
private func setupSession(sps: Data?, pps: Data?) -> Bool {
|
||||||
|
// 释放旧会话
|
||||||
|
if let session = decompressionSession {
|
||||||
|
VTDecompressionSessionInvalidate(session)
|
||||||
|
decompressionSession = nil
|
||||||
|
}
|
||||||
|
formatDesc = nil
|
||||||
|
isSessionReady = false
|
||||||
|
guard let sps = sps, let pps = pps else {
|
||||||
|
print("[VideoDecoder] 缺少SPS/PPS,无法初始化解码会话")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 校验SPS/PPS长度和类型
|
||||||
|
let spsType: UInt8 = sps.count > 0 ? (sps[0] & 0x1F) : 0
|
||||||
|
let ppsType: UInt8 = pps.count > 0 ? (pps[0] & 0x1F) : 0
|
||||||
|
if sps.count < 3 || spsType != 7 {
|
||||||
|
print("[VideoDecoder][错误] SPS内容异常,len=\(sps.count), type=\(spsType)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pps.count < 3 || ppsType != 8 {
|
||||||
|
print("[VideoDecoder][错误] PPS内容异常,len=\(pps.count), type=\(ppsType)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var success = false
|
||||||
|
sps.withUnsafeBytes { spsPtr in
|
||||||
|
pps.withUnsafeBytes { ppsPtr in
|
||||||
|
let parameterSetPointers: [UnsafePointer<UInt8>] = [
|
||||||
|
spsPtr.baseAddress!.assumingMemoryBound(to: UInt8.self),
|
||||||
|
ppsPtr.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||||
|
]
|
||||||
|
let parameterSetSizes: [Int] = [sps.count, pps.count]
|
||||||
|
let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
|
||||||
|
allocator: kCFAllocatorDefault,
|
||||||
|
parameterSetCount: 2,
|
||||||
|
parameterSetPointers: parameterSetPointers,
|
||||||
|
parameterSetSizes: parameterSetSizes,
|
||||||
|
nalUnitHeaderLength: 4,
|
||||||
|
formatDescriptionOut: &formatDesc
|
||||||
|
)
|
||||||
|
if status != noErr {
|
||||||
|
print("[VideoDecoder] 创建FormatDescription失败: \(status)")
|
||||||
|
success = false
|
||||||
|
} else {
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !success { return false }
|
||||||
|
var callback = VTDecompressionOutputCallbackRecord(
|
||||||
|
decompressionOutputCallback: { (decompressionOutputRefCon, _, status, _, imageBuffer, pts, _) in
|
||||||
|
let decoder = Unmanaged<VideoDecoder>.fromOpaque(decompressionOutputRefCon!).takeUnretainedValue()
|
||||||
|
if status == noErr, let pixelBuffer = imageBuffer {
|
||||||
|
// 入队到输出缓冲区,由渲染线程拉取
|
||||||
|
decoder.enqueueOutput((pixelBuffer, Int64(pts.seconds * 1000)))
|
||||||
|
} else {
|
||||||
|
print("[VideoDecoder] 解码回调失败, status=\(status)")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decompressionOutputRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
||||||
|
)
|
||||||
|
let attrs: [NSString: Any] = [
|
||||||
|
kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||||
|
kCVPixelBufferWidthKey: width,
|
||||||
|
kCVPixelBufferHeightKey: height,
|
||||||
|
kCVPixelBufferOpenGLESCompatibilityKey: true
|
||||||
|
]
|
||||||
|
let status2 = VTDecompressionSessionCreate(
|
||||||
|
allocator: kCFAllocatorDefault,
|
||||||
|
formatDescription: formatDesc!,
|
||||||
|
decoderSpecification: nil,
|
||||||
|
imageBufferAttributes: attrs as CFDictionary,
|
||||||
|
outputCallback: &callback,
|
||||||
|
decompressionSessionOut: &decompressionSession
|
||||||
|
)
|
||||||
|
if status2 != noErr {
|
||||||
|
print("[VideoDecoder] 创建解码会话失败: \(status2)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
isSessionReady = true
|
||||||
|
print("[VideoDecoder] 解码会话初始化成功")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解码一帧数据
|
||||||
|
func decodeFrame(frameData: Data, frameType: Int, timestamp: Int64, frameSeq: Int, refIFrameSeq: Int?, sps: Data? = nil, pps: Data? = nil) {
|
||||||
|
enqueueInput((frameData, frameType, timestamp, frameSeq, refIFrameSeq, sps, pps))
|
||||||
|
// 解码线程异步处理
|
||||||
|
decodeQueue.async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let (frameData, frameType, timestamp, frameSeq, refIFrameSeq, sps, pps) = self.dequeueInput() else { return }
|
||||||
|
if !self.isSessionReady, let sps = sps, let pps = pps {
|
||||||
|
guard self.setupSession(sps: sps, pps: pps) else { return }
|
||||||
|
}
|
||||||
|
guard let session = self.decompressionSession else { return }
|
||||||
|
guard frameData.count > 4 else { return }
|
||||||
|
var avccData = frameData
|
||||||
|
let naluLen = UInt32(frameData.count - 4).bigEndian
|
||||||
|
if avccData.count >= 4 {
|
||||||
|
avccData.replaceSubrange(0..<4, with: withUnsafeBytes(of: naluLen) { Data($0) })
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var blockBuffer: CMBlockBuffer?
|
||||||
|
let status = CMBlockBufferCreateWithMemoryBlock(
|
||||||
|
allocator: kCFAllocatorDefault,
|
||||||
|
memoryBlock: UnsafeMutableRawPointer(mutating: (avccData as NSData).bytes),
|
||||||
|
blockLength: avccData.count,
|
||||||
|
blockAllocator: kCFAllocatorNull,
|
||||||
|
customBlockSource: nil,
|
||||||
|
offsetToData: 0,
|
||||||
|
dataLength: avccData.count,
|
||||||
|
flags: 0,
|
||||||
|
blockBufferOut: &blockBuffer
|
||||||
|
)
|
||||||
|
if status != kCMBlockBufferNoErr { return }
|
||||||
|
var sampleBuffer: CMSampleBuffer?
|
||||||
|
var timing = CMSampleTimingInfo(duration: .invalid, presentationTimeStamp: CMTime(value: timestamp, timescale: 1000), decodeTimeStamp: .invalid)
|
||||||
|
let status2 = CMSampleBufferCreate(
|
||||||
|
allocator: kCFAllocatorDefault,
|
||||||
|
dataBuffer: blockBuffer,
|
||||||
|
dataReady: true,
|
||||||
|
makeDataReadyCallback: nil,
|
||||||
|
refcon: nil,
|
||||||
|
formatDescription: self.formatDesc,
|
||||||
|
sampleCount: 1,
|
||||||
|
sampleTimingEntryCount: 1,
|
||||||
|
sampleTimingArray: &timing,
|
||||||
|
sampleSizeEntryCount: 1,
|
||||||
|
sampleSizeArray: [avccData.count],
|
||||||
|
sampleBufferOut: &sampleBuffer
|
||||||
|
)
|
||||||
|
if status2 != noErr { return }
|
||||||
|
let decodeFlags: VTDecodeFrameFlags = []
|
||||||
|
var infoFlags = VTDecodeInfoFlags()
|
||||||
|
let status3 = VTDecompressionSessionDecodeFrame(
|
||||||
|
session,
|
||||||
|
sampleBuffer: sampleBuffer!,
|
||||||
|
flags: decodeFlags,
|
||||||
|
frameRefcon: nil,
|
||||||
|
infoFlagsOut: &infoFlags
|
||||||
|
)
|
||||||
|
if status3 != noErr {
|
||||||
|
print("[VideoDecoder] 解码失败: \(status3)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 释放解码器资源
|
||||||
|
func release() {
|
||||||
|
stopRenderThread()
|
||||||
|
decodeQueue.sync {
|
||||||
|
if let session = decompressionSession {
|
||||||
|
VTDecompressionSessionInvalidate(session)
|
||||||
|
}
|
||||||
|
decompressionSession = nil
|
||||||
|
formatDesc = nil
|
||||||
|
isSessionReady = false
|
||||||
|
frameSeqSet.removeAll()
|
||||||
|
lastIFrameSeq = nil
|
||||||
|
}
|
||||||
|
inputQueue.async(flags: .barrier) { self.inputBuffer.removeAll() }
|
||||||
|
outputQueue.async(flags: .barrier) { self.outputBuffer.removeAll() }
|
||||||
|
print("[VideoDecoder] 解码器已释放")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,11 +8,11 @@ class FrameDependencyManager {
|
|||||||
final List<int> _iFrameSeqWindow = [];
|
final List<int> _iFrameSeqWindow = [];
|
||||||
|
|
||||||
/// 更新SPS缓存
|
/// 更新SPS缓存
|
||||||
void updateSps(Uint8List sps) {
|
void updateSps(Uint8List? sps) {
|
||||||
_sps = sps;
|
_sps = sps;
|
||||||
}
|
}
|
||||||
/// 更新PPS缓存
|
/// 更新PPS缓存
|
||||||
void updatePps(Uint8List pps) {
|
void updatePps(Uint8List? pps) {
|
||||||
_pps = pps;
|
_pps = pps;
|
||||||
}
|
}
|
||||||
Uint8List? get sps => _sps;
|
Uint8List? get sps => _sps;
|
||||||
|
|||||||
@ -43,6 +43,11 @@ class VideoDecodePlugin {
|
|||||||
|
|
||||||
static int? _textureId;
|
static int? _textureId;
|
||||||
|
|
||||||
|
/// 获取默认纹理ID
|
||||||
|
static int? get textureId => _textureId;
|
||||||
|
|
||||||
|
static final _depManager = FrameDependencyManager();
|
||||||
|
|
||||||
/// onFrameRendered回调类型(解码并已开始渲染)
|
/// onFrameRendered回调类型(解码并已开始渲染)
|
||||||
static void Function(int textureId)? _onFrameRendered;
|
static void Function(int textureId)? _onFrameRendered;
|
||||||
|
|
||||||
@ -98,6 +103,8 @@ class VideoDecodePlugin {
|
|||||||
'textureId': _textureId,
|
'textureId': _textureId,
|
||||||
});
|
});
|
||||||
_textureId = null;
|
_textureId = null;
|
||||||
|
_depManager.updateSps(null);
|
||||||
|
_depManager.updatePps(null);
|
||||||
return result ?? false;
|
return result ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,11 +118,6 @@ class VideoDecodePlugin {
|
|||||||
return Platform.isAndroid || Platform.isIOS;
|
return Platform.isAndroid || Platform.isIOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取默认纹理ID
|
|
||||||
static int? get textureId => _textureId;
|
|
||||||
|
|
||||||
static final _depManager = FrameDependencyManager();
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// [frameData]:帧数据
|
/// [frameData]:帧数据
|
||||||
/// [frameType]:帧类型 0=I帧, 1=P帧
|
/// [frameType]:帧类型 0=I帧, 1=P帧
|
||||||
@ -130,6 +132,35 @@ class VideoDecodePlugin {
|
|||||||
required int frameSeq,
|
required int frameSeq,
|
||||||
bool splitNalFromIFrame = false,
|
bool splitNalFromIFrame = false,
|
||||||
int? refIFrameSeq, // P帧时可选
|
int? refIFrameSeq, // P帧时可选
|
||||||
|
}) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
await _sendFrameAndroid(
|
||||||
|
frameData: frameData,
|
||||||
|
frameType: frameType,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq,
|
||||||
|
splitNalFromIFrame: splitNalFromIFrame,
|
||||||
|
refIFrameSeq: refIFrameSeq,
|
||||||
|
);
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
await _sendFrameIOS(
|
||||||
|
frameData: frameData,
|
||||||
|
frameType: frameType,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq,
|
||||||
|
refIFrameSeq: refIFrameSeq,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 其他平台暂不支持
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _sendFrameAndroid({
|
||||||
|
required List<int> frameData,
|
||||||
|
required int frameType,
|
||||||
|
required int timestamp,
|
||||||
|
required int frameSeq,
|
||||||
|
bool splitNalFromIFrame = false,
|
||||||
|
int? refIFrameSeq,
|
||||||
}) async {
|
}) async {
|
||||||
if (splitNalFromIFrame && frameType == 0) {
|
if (splitNalFromIFrame && frameType == 0) {
|
||||||
// 优先使用缓存的SPS/PPS
|
// 优先使用缓存的SPS/PPS
|
||||||
@ -220,4 +251,96 @@ class VideoDecodePlugin {
|
|||||||
// 若为I帧,更新依赖管理
|
// 若为I帧,更新依赖管理
|
||||||
if (frameType == 0) _depManager.updateIFrameSeq(frameSeq);
|
if (frameType == 0) _depManager.updateIFrameSeq(frameSeq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> _sendFrameIOS({
|
||||||
|
required List<int> frameData,
|
||||||
|
required int frameType,
|
||||||
|
required int timestamp,
|
||||||
|
required int frameSeq,
|
||||||
|
int? refIFrameSeq,
|
||||||
|
}) async {
|
||||||
|
// 仅P帧做AnnexB起始码检测和修正
|
||||||
|
if (frameType == 1) {
|
||||||
|
// 分割NALU并只保留type=1的NALU
|
||||||
|
final nalus = NaluUtils.splitNalus(frameData);
|
||||||
|
final pNalus = nalus.where((nalu) => nalu.type == 1).toList();
|
||||||
|
if (pNalus.isEmpty) {
|
||||||
|
print('[VideoDecodePlugin][警告] iOS端P帧未找到type=1的NALU,已丢弃');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 拼接所有type=1的NALU
|
||||||
|
final sendData = <int>[];
|
||||||
|
for (final nalu in pNalus) {
|
||||||
|
sendData.addAll(nalu.data);
|
||||||
|
}
|
||||||
|
// print(
|
||||||
|
// '[VideoDecodePlugin][调试] iOS端P帧仅推送type=1 NALU, count=${pNalus.length}, 总长度=${sendData.length}');
|
||||||
|
frameData = sendData;
|
||||||
|
int startIndex = -1;
|
||||||
|
for (int i = 0; i < frameData.length - 3; i++) {
|
||||||
|
if (frameData[i] == 0x00 &&
|
||||||
|
frameData[i + 1] == 0x00 &&
|
||||||
|
frameData[i + 2] == 0x00 &&
|
||||||
|
frameData[i + 3] == 0x01) {
|
||||||
|
startIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startIndex == -1) {
|
||||||
|
print('[VideoDecodePlugin][警告] iOS端P帧仅type=1 NALU后无AnnexB起始码,已丢弃');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (startIndex > 0) {
|
||||||
|
frameData = frameData.sublist(startIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (frameType == 0) {
|
||||||
|
// I帧需分割NALU,严格按SPS->PPS->I帧顺序推送
|
||||||
|
final nalus = NaluUtils.splitNalus(frameData);
|
||||||
|
List<int>? sps, pps, idr;
|
||||||
|
for (final nalu in nalus) {
|
||||||
|
if (nalu.type == 7) sps = nalu.data;
|
||||||
|
if (nalu.type == 8) pps = nalu.data;
|
||||||
|
if (nalu.type == 5) idr = nalu.data;
|
||||||
|
}
|
||||||
|
if (sps != null) {
|
||||||
|
await _decodeFrame(
|
||||||
|
frameData: Uint8List.fromList(sps),
|
||||||
|
frameType: 0,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq,
|
||||||
|
refIFrameSeq: frameSeq,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pps != null) {
|
||||||
|
await _decodeFrame(
|
||||||
|
frameData: Uint8List.fromList(pps),
|
||||||
|
frameType: 0,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq,
|
||||||
|
refIFrameSeq: frameSeq,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (idr != null) {
|
||||||
|
await _decodeFrame(
|
||||||
|
frameData: Uint8List.fromList(idr),
|
||||||
|
frameType: 0,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq,
|
||||||
|
refIFrameSeq: frameSeq,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_depManager.updateIFrameSeq(frameSeq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 其他类型帧直接推送
|
||||||
|
await _decodeFrame(
|
||||||
|
frameData: Uint8List.fromList(frameData),
|
||||||
|
frameType: frameType,
|
||||||
|
timestamp: timestamp,
|
||||||
|
frameSeq: frameSeq,
|
||||||
|
refIFrameSeq: refIFrameSeq,
|
||||||
|
);
|
||||||
|
if (frameType == 0) _depManager.updateIFrameSeq(frameSeq);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user