mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
fix(ios): use MobileVLCKit from CocoaPods instead of a git Swift package
Android Studio couldn't build the iOS app: resolving the videolan/vlckit Swift package fails inside it (Xcode's source control service reports "Communication with the service was interrupted"), ending in "Missing package product 'VLCKit'". VLCKit's README only documents CocoaPods and Carthage anyway. - Podfile with MobileVLCKit 3.7.5 (iOS-only build, 233 MB instead of the 922 MB cross-platform VLCKit 4 binary); build iosApp.xcworkspace. - VlcPlayerEngine ported to the MobileVLCKit 3 API: it has a real "ended" state, so the stop bookkeeping and the post-resume seek workaround needed for VLCKit 4 are gone. Buffering/ES-added notifications keep arriving while playing, so "playing" is reported whenever VLC is playing. - NativePlayerListener.onBuffered removed: MobileVLCKit has no buffering progress callback. - Pods/ is ignored; AGENTS.md notes the pod install + workspace setup.
This commit is contained in:
parent
f9c040015e
commit
e02c1b73b4
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ captures
|
|||||||
!*.xcodeproj/project.xcworkspace/
|
!*.xcodeproj/project.xcworkspace/
|
||||||
!*.xcworkspace/contents.xcworkspacedata
|
!*.xcworkspace/contents.xcworkspacedata
|
||||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||||
|
iosApp/Pods/
|
||||||
node_modules/
|
node_modules/
|
||||||
composeApp/release/
|
composeApp/release/
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
- Desktop/JVM: `composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt` (`mainClass = org.spotube.spotube.MainKt`).
|
- Desktop/JVM: `composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt` (`mainClass = org.spotube.spotube.MainKt`).
|
||||||
- Android: `composeApp/src/androidMain/kotlin/org/spotube/spotube/MainActivity.kt`.
|
- Android: `composeApp/src/androidMain/kotlin/org/spotube/spotube/MainActivity.kt`.
|
||||||
- iOS bridge: `composeApp/src/iosMain/kotlin/org/spotube/spotube/MainViewController.kt` and `iosApp/iosApp/ContentView.swift`.
|
- iOS bridge: `composeApp/src/iosMain/kotlin/org/spotube/spotube/MainViewController.kt` and `iosApp/iosApp/ContentView.swift`.
|
||||||
|
- iOS audio: MobileVLCKit from CocoaPods (`iosApp/Podfile`), driven by `iosApp/iosApp/VlcPlayerEngine.swift` through `NativePlayerEngine` (iosMain). Run `pod install` in `iosApp/` first, then build `iosApp/iosApp.xcworkspace` (not the `.xcodeproj`, which can't build the Pods). Android Studio's iOS run config must point at the workspace too.
|
||||||
|
|
||||||
## High-value commands
|
## High-value commands
|
||||||
- Use Gradle wrapper (`./gradlew` or `./gradlew.bat`) only.
|
- Use Gradle wrapper (`./gradlew` or `./gradlew.bat`) only.
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import kotlin.time.Duration
|
|||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iOS player. Playback is done by VLCKit through [NativePlayerEngine]; AVPlayer misreports the
|
* iOS player. Playback is done by MobileVLCKit through [NativePlayerEngine]; AVPlayer misreports the
|
||||||
* duration of some fragmented MP4 streams (doubling it, which leaves the second half silent).
|
* duration of some fragmented MP4 streams (doubling it, which leaves the second half silent).
|
||||||
* The engine plays a single URL, so the playlist, loop, shuffle and auto-advance live here.
|
* The engine plays a single URL, so the playlist, loop, shuffle and auto-advance live here.
|
||||||
*/
|
*/
|
||||||
@ -110,15 +110,6 @@ actual class AudioPlayer actual constructor(context: Any) : AudioPlayerInterface
|
|||||||
_duration.tryEmit(durationMs.milliseconds)
|
_duration.tryEmit(durationMs.milliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBuffered(percent: Float) {
|
|
||||||
if (disposed) return
|
|
||||||
val durationMs = _duration.value.inWholeMilliseconds
|
|
||||||
_bufferingPosition.tryEmit(
|
|
||||||
if (durationMs > 0) (durationMs * (percent.coerceIn(0f, 100f) / 100f)).toLong().milliseconds
|
|
||||||
else Duration.ZERO
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(message: String) {
|
override fun onError(message: String) {
|
||||||
if (disposed) return
|
if (disposed) return
|
||||||
_playerState.tryEmit(PlayerState.IDLE)
|
_playerState.tryEmit(PlayerState.IDLE)
|
||||||
|
|||||||
@ -18,14 +18,15 @@
|
|||||||
package org.spotube.spotube.core.audioplayer
|
package org.spotube.spotube.core.audioplayer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single-media playback engine implemented in Swift on top of VLCKit (`iosApp/iosApp/VlcPlayerEngine.swift`).
|
* Single-media playback engine implemented in Swift on top of MobileVLCKit
|
||||||
|
* (`iosApp/iosApp/VlcPlayerEngine.swift`).
|
||||||
*
|
*
|
||||||
* VLCKit ships as a SwiftPM binary target, so it can't be bound with cinterop from Gradle. Swift
|
* MobileVLCKit comes from CocoaPods into the Xcode project, so Gradle can't bind it with cinterop.
|
||||||
* implements this contract instead and registers it in [NativePlayerRegistry] before the Compose
|
* Swift implements this contract instead and registers it in [NativePlayerRegistry] before the
|
||||||
* view controller is created. The playlist, loop and shuffle logic stays in Kotlin
|
* Compose view controller is created. The playlist, loop and shuffle logic stays in Kotlin
|
||||||
* ([AudioPlayer]); the engine only ever plays one URL at a time.
|
* ([AudioPlayer]); the engine only ever plays one URL at a time.
|
||||||
*
|
*
|
||||||
* Implementations may be called from any thread and must hop to the thread VLCKit needs
|
* Implementations may be called from any thread and must hop to the thread VLC needs
|
||||||
* themselves. [NativePlayerListener] callbacks may arrive on any thread.
|
* themselves. [NativePlayerListener] callbacks may arrive on any thread.
|
||||||
*/
|
*/
|
||||||
interface NativePlayerEngine {
|
interface NativePlayerEngine {
|
||||||
@ -53,9 +54,6 @@ interface NativePlayerListener {
|
|||||||
fun onState(state: NativePlayerState)
|
fun onState(state: NativePlayerState)
|
||||||
fun onTime(positionMs: Long)
|
fun onTime(positionMs: Long)
|
||||||
fun onDuration(durationMs: Long)
|
fun onDuration(durationMs: Long)
|
||||||
|
|
||||||
/** Network cache fill of the current media, 0..100. */
|
|
||||||
fun onBuffered(percent: Float)
|
|
||||||
fun onError(message: String)
|
fun onError(message: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
iosApp/Podfile
Normal file
9
iosApp/Podfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# MobileVLCKit plays audio on iOS (iosApp/iosApp/VlcPlayerEngine.swift).
|
||||||
|
# After `pod install`, open and build iosApp.xcworkspace, not iosApp.xcodeproj.
|
||||||
|
platform :ios, '18.2'
|
||||||
|
|
||||||
|
target 'iosApp' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
pod 'MobileVLCKit', '3.7.5'
|
||||||
|
end
|
||||||
16
iosApp/Podfile.lock
Normal file
16
iosApp/Podfile.lock
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
PODS:
|
||||||
|
- MobileVLCKit (3.7.5)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- MobileVLCKit (= 3.7.5)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- MobileVLCKit
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
MobileVLCKit: 9fb4475cd820b2d271ac394d5d7ba18c525332ab
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 6dfb07dbc8a64d0c6fe7231a81687c56f08e8250
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
@ -7,11 +7,14 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
9A1B2C3D4E5F60718293A4B1 /* VLCKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1B2C3D4E5F60718293A4B3 /* VLCKit */; };
|
EC1521163DBA0344F6C5A520 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F2995B965BD790B4CF9EE4E /* Pods_iosApp.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
11DF02E4B44E5BCF61736473 /* Spotube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Spotube.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
11DF02E4B44E5BCF61736473 /* Spotube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Spotube.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
34377C7B259B7315C03BE940 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
3F2995B965BD790B4CF9EE4E /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
BA9738EB07A613714DD0F50B /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@ -35,6 +38,8 @@
|
|||||||
};
|
};
|
||||||
E265D0F6D10F52F5C1374EEB /* Configuration */ = {
|
E265D0F6D10F52F5C1374EEB /* Configuration */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = Configuration;
|
path = Configuration;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@ -45,22 +50,13 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
9A1B2C3D4E5F60718293A4B1 /* VLCKit in Frameworks */,
|
EC1521163DBA0344F6C5A520 /* Pods_iosApp.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
EFA169D322B90F32841D602F = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E265D0F6D10F52F5C1374EEB /* Configuration */,
|
|
||||||
5BB730BF90365122B74925FF /* iosApp */,
|
|
||||||
835615636B5FE86AA850B7DB /* Products */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
835615636B5FE86AA850B7DB /* Products */ = {
|
835615636B5FE86AA850B7DB /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -69,6 +65,35 @@
|
|||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
85BADDD666F59900213B22FE /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3F2995B965BD790B4CF9EE4E /* Pods_iosApp.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
92A495F3BC0A87A606BB8FD4 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
34377C7B259B7315C03BE940 /* Pods-iosApp.debug.xcconfig */,
|
||||||
|
BA9738EB07A613714DD0F50B /* Pods-iosApp.release.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EFA169D322B90F32841D602F = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E265D0F6D10F52F5C1374EEB /* Configuration */,
|
||||||
|
5BB730BF90365122B74925FF /* iosApp */,
|
||||||
|
835615636B5FE86AA850B7DB /* Products */,
|
||||||
|
92A495F3BC0A87A606BB8FD4 /* Pods */,
|
||||||
|
85BADDD666F59900213B22FE /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -76,10 +101,12 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 6F8AC65FA015674062B91E20 /* Build configuration list for PBXNativeTarget "iosApp" */;
|
buildConfigurationList = 6F8AC65FA015674062B91E20 /* Build configuration list for PBXNativeTarget "iosApp" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
FD8DD8DB9F90C61C8EB92C92 /* [CP] Check Pods Manifest.lock */,
|
||||||
6413B0F29E004BC8B0C4D22A /* Compile Kotlin Framework */,
|
6413B0F29E004BC8B0C4D22A /* Compile Kotlin Framework */,
|
||||||
314349EF3C4149F4A4A72378 /* Sources */,
|
314349EF3C4149F4A4A72378 /* Sources */,
|
||||||
EFCF243BC7D8246E008EF23D /* Frameworks */,
|
EFCF243BC7D8246E008EF23D /* Frameworks */,
|
||||||
ED9557BFD7438D37A5E9513D /* Resources */,
|
ED9557BFD7438D37A5E9513D /* Resources */,
|
||||||
|
19DB94093A1327B00D11FC9C /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -89,9 +116,6 @@
|
|||||||
5BB730BF90365122B74925FF /* iosApp */,
|
5BB730BF90365122B74925FF /* iosApp */,
|
||||||
);
|
);
|
||||||
name = iosApp;
|
name = iosApp;
|
||||||
packageProductDependencies = (
|
|
||||||
9A1B2C3D4E5F60718293A4B3 /* VLCKit */,
|
|
||||||
);
|
|
||||||
productName = iosApp;
|
productName = iosApp;
|
||||||
productReference = 11DF02E4B44E5BCF61736473 /* Spotube.app */;
|
productReference = 11DF02E4B44E5BCF61736473 /* Spotube.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
@ -120,9 +144,6 @@
|
|||||||
);
|
);
|
||||||
mainGroup = EFA169D322B90F32841D602F;
|
mainGroup = EFA169D322B90F32841D602F;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
|
||||||
9A1B2C3D4E5F60718293A4B2 /* XCRemoteSwiftPackageReference "vlckit" */,
|
|
||||||
);
|
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = 835615636B5FE86AA850B7DB /* Products */;
|
productRefGroup = 835615636B5FE86AA850B7DB /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -144,6 +165,23 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
19DB94093A1327B00D11FC9C /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
6413B0F29E004BC8B0C4D22A /* Compile Kotlin Framework */ = {
|
6413B0F29E004BC8B0C4D22A /* Compile Kotlin Framework */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@ -163,6 +201,28 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
|
shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
|
||||||
};
|
};
|
||||||
|
FD8DD8DB9F90C61C8EB92C92 /* [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-iosApp-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;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@ -176,6 +236,122 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
|
1C42200356BD14712FAE1CFA /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReferenceAnchor = E265D0F6D10F52F5C1374EEB /* Configuration */;
|
||||||
|
baseConfigurationReferenceRelativePath = Config.xcconfig;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
4FB276594B17F1A0D71D2462 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BA9738EB07A613714DD0F50B /* Pods-iosApp.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = arm64;
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = "${TEAM_ID}";
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = iosApp/Info.plist;
|
||||||
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
AC996E9458BD3DAE9610EA49 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 34377C7B259B7315C03BE940 /* Pods-iosApp.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = arm64;
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = "${TEAM_ID}";
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = iosApp/Info.plist;
|
||||||
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
AF5828948B37FEEF5EF7F345 /* Debug */ = {
|
AF5828948B37FEEF5EF7F345 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReferenceAnchor = E265D0F6D10F52F5C1374EEB /* Configuration */;
|
baseConfigurationReferenceAnchor = E265D0F6D10F52F5C1374EEB /* Configuration */;
|
||||||
@ -241,132 +417,9 @@
|
|||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
1C42200356BD14712FAE1CFA /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReferenceAnchor = E265D0F6D10F52F5C1374EEB /* Configuration */;
|
|
||||||
baseConfigurationReferenceRelativePath = Config.xcconfig;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
AC996E9458BD3DAE9610EA49 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ARCHS = arm64;
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
|
||||||
DEVELOPMENT_TEAM = "${TEAM_ID}";
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
INFOPLIST_FILE = iosApp/Info.plist;
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
);
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
4FB276594B17F1A0D71D2462 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ARCHS = arm64;
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
|
||||||
DEVELOPMENT_TEAM = "${TEAM_ID}";
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
INFOPLIST_FILE = iosApp/Info.plist;
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
);
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
79A44D8DEA428263AC7DC2E1 /* Build configuration list for PBXProject "iosApp" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
AF5828948B37FEEF5EF7F345 /* Debug */,
|
|
||||||
1C42200356BD14712FAE1CFA /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
6F8AC65FA015674062B91E20 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
|
6F8AC65FA015674062B91E20 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
@ -376,26 +429,16 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
79A44D8DEA428263AC7DC2E1 /* Build configuration list for PBXProject "iosApp" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
AF5828948B37FEEF5EF7F345 /* Debug */,
|
||||||
|
1C42200356BD14712FAE1CFA /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
|
||||||
9A1B2C3D4E5F60718293A4B2 /* XCRemoteSwiftPackageReference "vlckit" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/videolan/vlckit";
|
|
||||||
requirement = {
|
|
||||||
kind = revision;
|
|
||||||
revision = 6b87735a95fd3aa96f0fdb6d1b465a2d92badd8f;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
|
||||||
9A1B2C3D4E5F60718293A4B3 /* VLCKit */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 9A1B2C3D4E5F60718293A4B2 /* XCRemoteSwiftPackageReference "vlckit" */;
|
|
||||||
productName = VLCKit;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
|
||||||
};
|
};
|
||||||
rootObject = B056E79C138AE849968965F1 /* Project object */;
|
rootObject = B056E79C138AE849968965F1 /* Project object */;
|
||||||
}
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"originHash" : "1299735cf5953086f977e465ed8fbd8efae67129992b979462f68d933745ea6b",
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "vlckit",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/videolan/vlckit",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "6b87735a95fd3aa96f0fdb6d1b465a2d92badd8f"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 3
|
|
||||||
}
|
|
||||||
10
iosApp/iosApp.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
iosApp/iosApp.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:iosApp.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -16,9 +16,9 @@
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
import ComposeApp
|
import ComposeApp
|
||||||
import Foundation
|
import Foundation
|
||||||
import VLCKit
|
import MobileVLCKit
|
||||||
|
|
||||||
/// Registers the VLCKit engine with the shared Kotlin `AudioPlayer`. Call before the first
|
/// Registers the MobileVLCKit engine with the shared Kotlin `AudioPlayer`. Call before the first
|
||||||
/// `ComposeView` is created.
|
/// `ComposeView` is created.
|
||||||
func registerNativePlayerEngine() {
|
func registerNativePlayerEngine() {
|
||||||
NativePlayerRegistry.shared.factory = VlcPlayerEngineFactory()
|
NativePlayerRegistry.shared.factory = VlcPlayerEngineFactory()
|
||||||
@ -30,30 +30,23 @@ final class VlcPlayerEngineFactory: NSObject, NativePlayerEngineFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays one URL at a time with VLCKit. The Kotlin `AudioPlayer` owns the playlist; see
|
/// Plays one URL at a time with MobileVLCKit. The Kotlin `AudioPlayer` owns the playlist; see
|
||||||
/// `NativePlayerEngine.kt` for the contract.
|
/// `NativePlayerEngine.kt` for the contract.
|
||||||
///
|
///
|
||||||
/// Every VLCKit call is made on the main queue, in the order the Kotlin side issued it.
|
/// Every VLC call is made on the main queue, in the order the Kotlin side issued it.
|
||||||
final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegate {
|
final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegate {
|
||||||
private let player = VLCMediaPlayer()
|
private let player = VLCMediaPlayer()
|
||||||
private var listener: NativePlayerListener?
|
private var listener: NativePlayerListener?
|
||||||
|
|
||||||
// VLCKit 4 has no "ended" state: reaching the end of a media goes Stopping -> Stopped, the
|
|
||||||
// same as stop(). `stopRequested` marks the stops this class asked for, so any other stop of
|
|
||||||
// a media that had started playing is a natural end.
|
|
||||||
private var stopRequested = false
|
|
||||||
private var hasStarted = false
|
|
||||||
private var finished = false
|
|
||||||
private var released = false
|
private var released = false
|
||||||
|
|
||||||
private var rate: Float = 1
|
private var rate: Float = 1
|
||||||
private var volumePercent: Int32 = 100
|
private var volumePercent: Int32 = 100
|
||||||
|
private var lastReportedDurationMs: Int64 = 0
|
||||||
|
private var lastReportedState: NativePlayerState?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
player.delegate = self
|
player.delegate = self
|
||||||
player.timeChangeUpdateInterval = 0.25
|
|
||||||
player.minimalTimePeriod = 250_000
|
|
||||||
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
|
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,19 +59,18 @@ final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegat
|
|||||||
func load(url: String, autoPlay: Bool, startPositionMs: Int64) {
|
func load(url: String, autoPlay: Bool, startPositionMs: Int64) {
|
||||||
onMain {
|
onMain {
|
||||||
guard !self.released else { return }
|
guard !self.released else { return }
|
||||||
guard let mediaUrl = URL(string: url), let media = VLCMedia(url: mediaUrl) else {
|
guard let mediaUrl = URL(string: url) else {
|
||||||
self.listener?.onError(message: "Invalid URL: \(url)")
|
self.listener?.onError(message: "Invalid URL: \(url)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stopCurrentMedia()
|
let media = VLCMedia(url: mediaUrl)
|
||||||
self.hasStarted = false
|
|
||||||
self.finished = false
|
|
||||||
if startPositionMs > 0 {
|
if startPositionMs > 0 {
|
||||||
media.addOption(":start-time=\(Double(startPositionMs) / 1000.0)")
|
media.addOption(":start-time=\(Double(startPositionMs) / 1000.0)")
|
||||||
}
|
}
|
||||||
|
self.lastReportedDurationMs = 0
|
||||||
self.player.media = media
|
self.player.media = media
|
||||||
self.listener?.onState(state: .opening)
|
self.report(.opening)
|
||||||
if autoPlay { self.startPlayback() }
|
if autoPlay { self.startPlayback() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +92,7 @@ final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegat
|
|||||||
func stop() {
|
func stop() {
|
||||||
onMain {
|
onMain {
|
||||||
guard !self.released else { return }
|
guard !self.released else { return }
|
||||||
self.stopCurrentMedia()
|
self.player.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,25 +131,26 @@ final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegat
|
|||||||
|
|
||||||
// MARK: VLCMediaPlayerDelegate
|
// MARK: VLCMediaPlayerDelegate
|
||||||
|
|
||||||
func mediaPlayerStateChanged(_ newState: VLCMediaPlayerState) {
|
func mediaPlayerStateChanged(_ aNotification: Notification) {
|
||||||
onMain {
|
onMain {
|
||||||
guard !self.released else { return }
|
guard !self.released else { return }
|
||||||
switch newState {
|
switch self.player.state {
|
||||||
case .opening:
|
case .opening:
|
||||||
self.listener?.onState(state: .opening)
|
self.report(.opening)
|
||||||
|
case .buffering, .esAdded:
|
||||||
|
// MobileVLCKit keeps sending these while it plays; only a stall counts as buffering.
|
||||||
|
self.report(self.player.isPlaying ? .playing : .buffering)
|
||||||
case .playing:
|
case .playing:
|
||||||
self.hasStarted = true
|
self.report(.playing)
|
||||||
self.applyPlaybackSettings()
|
|
||||||
self.reportDuration()
|
|
||||||
self.listener?.onState(state: .playing)
|
|
||||||
case .paused:
|
case .paused:
|
||||||
self.listener?.onState(state: .paused)
|
self.report(.paused)
|
||||||
case .stopping, .stopped:
|
case .ended:
|
||||||
self.handleStop(isFinalStop: newState == .stopped)
|
self.report(.ended)
|
||||||
|
case .stopped:
|
||||||
|
self.report(.idle)
|
||||||
case .error:
|
case .error:
|
||||||
self.finished = true
|
|
||||||
self.listener?.onError(message: "VLC could not play this media")
|
self.listener?.onError(message: "VLC could not play this media")
|
||||||
self.listener?.onState(state: .idle)
|
self.report(.idle)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -165,29 +158,13 @@ final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mediaPlayerTimeChanged(_ aNotification: Notification) {
|
func mediaPlayerTimeChanged(_ aNotification: Notification) {
|
||||||
onMain {
|
|
||||||
guard !self.released, let ms = self.player.time.value?.int64Value, ms >= 0 else { return }
|
|
||||||
self.listener?.onTime(positionMs: ms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mediaPlayerLengthChanged(_ length: Int64) {
|
|
||||||
onMain {
|
onMain {
|
||||||
guard !self.released else { return }
|
guard !self.released else { return }
|
||||||
self.listener?.onDuration(durationMs: length)
|
// Time only advances while playing, whatever the last state notification said.
|
||||||
}
|
if self.player.isPlaying { self.report(.playing) }
|
||||||
}
|
self.reportDuration()
|
||||||
|
if let ms = self.player.time.value?.int64Value, ms >= 0 {
|
||||||
func mediaPlayerBufferingChanged(_ progress: Float) {
|
self.listener?.onTime(positionMs: ms)
|
||||||
onMain {
|
|
||||||
guard !self.released else { return }
|
|
||||||
self.listener?.onBuffered(percent: progress * 100)
|
|
||||||
// VLC always reports 0 and 1 before the first frame; only mid-playback stalls matter.
|
|
||||||
guard self.hasStarted else { return }
|
|
||||||
if progress < 1 {
|
|
||||||
self.listener?.onState(state: .buffering)
|
|
||||||
} else if self.player.isPlaying {
|
|
||||||
self.listener?.onState(state: .playing)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,47 +173,18 @@ final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegat
|
|||||||
|
|
||||||
private func startPlayback() {
|
private func startPlayback() {
|
||||||
try? AVAudioSession.sharedInstance().setActive(true)
|
try? AVAudioSession.sharedInstance().setActive(true)
|
||||||
let wasPaused = player.state == .paused
|
|
||||||
player.play()
|
player.play()
|
||||||
if wasPaused { kickTimeReporting() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// After a pause, VLCKit 4 withholds time updates until libvlc reports a fresh time point, and
|
/// Sends [state] to Kotlin unless it was the last one sent.
|
||||||
/// on iOS that report doesn't come after a resume: audio plays but the position stays frozen.
|
private func report(_ state: NativePlayerState) {
|
||||||
/// A seek goes through a different callback that does reset VLCKit's time tracking, so seek
|
guard state != lastReportedState else { return }
|
||||||
/// to where we already are.
|
lastReportedState = state
|
||||||
private func kickTimeReporting() {
|
if state == .playing {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
applyPlaybackSettings()
|
||||||
guard let self, !self.released, self.player.isPlaying, self.player.isSeekable else { return }
|
reportDuration()
|
||||||
self.player.time = self.player.time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stops the current media and marks the resulting stop events as ours.
|
|
||||||
private func stopCurrentMedia() {
|
|
||||||
switch player.state {
|
|
||||||
case .opening, .playing, .paused, .stopping:
|
|
||||||
stopRequested = true
|
|
||||||
player.stop()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleStop(isFinalStop: Bool) {
|
|
||||||
if stopRequested {
|
|
||||||
if isFinalStop {
|
|
||||||
stopRequested = false
|
|
||||||
listener?.onState(state: .idle)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if hasStarted && !finished {
|
|
||||||
finished = true
|
|
||||||
listener?.onState(state: .ended)
|
|
||||||
} else if isFinalStop && !finished {
|
|
||||||
listener?.onState(state: .idle)
|
|
||||||
}
|
}
|
||||||
|
listener?.onState(state: state)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func applyPlaybackSettings() {
|
private func applyPlaybackSettings() {
|
||||||
@ -244,10 +192,11 @@ final class VlcPlayerEngine: NSObject, NativePlayerEngine, VLCMediaPlayerDelegat
|
|||||||
if player.rate != rate { player.rate = rate }
|
if player.rate != rate { player.rate = rate }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// MobileVLCKit has no length callback, so the length is read from the media as it plays.
|
||||||
private func reportDuration() {
|
private func reportDuration() {
|
||||||
if let ms = player.media?.length.value?.int64Value, ms > 0 {
|
guard let ms = player.media?.length.value?.int64Value, ms > 0, ms != lastReportedDurationMs else { return }
|
||||||
listener?.onDuration(durationMs: ms)
|
lastReportedDurationMs = ms
|
||||||
}
|
listener?.onDuration(durationMs: ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func onMain(_ work: @escaping () -> Void) {
|
private func onMain(_ work: @escaping () -> Void) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user