From 09e9462ac04e4adfcaa96e0bd2cae5394c7a8a28 Mon Sep 17 00:00:00 2001 From: herrrta <73949927+herrrta@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:10:15 -0500 Subject: [PATCH] feat: (iOS) Switch Video Players --- .gitattributes | 1 + .gitignore | 1 + app/(auth)/player/direct-player.tsx | 4 +- components/settings/OtherSettings.tsx | 33 +- components/video-player/controls/Controls.tsx | 89 ++-- .../controls/contexts/ControlContext.tsx | 1 - .../controls/contexts/VideoContext.tsx | 2 +- components/vlc/VideoDebugInfo.tsx | 2 +- .../{vlc-player/src => }/VlcPlayer.types.ts | 0 .../{vlc-player/src => }/VlcPlayerView.tsx | 18 +- modules/index.ts | 27 ++ modules/vlc-player-3/expo-module.config.json | 6 + modules/vlc-player-3/ios/VlcPlayer3.podspec | 23 ++ .../vlc-player-3/ios/VlcPlayer3Module.swift | 71 ++++ modules/vlc-player-3/ios/VlcPlayer3View.swift | 388 ++++++++++++++++++ modules/vlc-player-3/src/VlcPlayer3Module.ts | 5 + .../.gradle/8.9/checksums/checksums.lock | Bin 17 -> 0 bytes .../8.9/dependencies-accessors/gc.properties | 0 .../.gradle/8.9/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/8.9/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../android/.gradle/8.9/gc.properties | 0 .../buildOutputCleanup.lock | Bin 17 -> 0 bytes .../buildOutputCleanup/cache.properties | 2 - .../android/.gradle/vcs-1/gc.properties | 0 modules/vlc-player/index.ts | 68 --- modules/vlc-player/ios/VlcPlayer.podspec | 3 +- modules/vlc-player/ios/VlcPlayerView.swift | 1 - translations/de.json | 7 +- translations/en.json | 7 +- translations/es.json | 7 +- translations/fr.json | 7 +- translations/it.json | 7 +- translations/ja.json | 7 +- translations/nl.json | 7 +- translations/tr.json | 7 +- translations/zh-CN.json | 7 +- translations/zh-TW.json | 7 +- utils/atoms/settings.ts | 9 + 38 files changed, 676 insertions(+), 148 deletions(-) create mode 100644 .gitattributes rename modules/{vlc-player/src => }/VlcPlayer.types.ts (100%) rename modules/{vlc-player/src => }/VlcPlayerView.tsx (86%) create mode 100644 modules/index.ts create mode 100644 modules/vlc-player-3/expo-module.config.json create mode 100644 modules/vlc-player-3/ios/VlcPlayer3.podspec create mode 100644 modules/vlc-player-3/ios/VlcPlayer3Module.swift create mode 100644 modules/vlc-player-3/ios/VlcPlayer3View.swift create mode 100644 modules/vlc-player-3/src/VlcPlayer3Module.ts delete mode 100644 modules/vlc-player/android/.gradle/8.9/checksums/checksums.lock delete mode 100644 modules/vlc-player/android/.gradle/8.9/dependencies-accessors/gc.properties delete mode 100644 modules/vlc-player/android/.gradle/8.9/fileChanges/last-build.bin delete mode 100644 modules/vlc-player/android/.gradle/8.9/fileHashes/fileHashes.lock delete mode 100644 modules/vlc-player/android/.gradle/8.9/gc.properties delete mode 100644 modules/vlc-player/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 modules/vlc-player/android/.gradle/buildOutputCleanup/cache.properties delete mode 100644 modules/vlc-player/android/.gradle/vcs-1/gc.properties delete mode 100644 modules/vlc-player/index.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..56dea966 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.modules/vlc-player/Frameworks/*.xcframework filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 2a0ce8db..00eb8098 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ npm-debug.* *.orig.* web-build/ modules/vlc-player/android/build +modules/vlc-player/android/.gradle bun.lockb # macOS diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 9a407ade..83bda8ce 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -5,13 +5,13 @@ import { Controls } from "@/components/video-player/controls/Controls"; import { getDownloadedFileUrl } from "@/hooks/useDownloadedFileOpener"; import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache"; import { useWebSocket } from "@/hooks/useWebsockets"; -import { VlcPlayerView } from "@/modules/vlc-player"; +import { VlcPlayerView } from "@/modules"; import { PipStartedPayload, PlaybackStatePayload, ProgressUpdatePayload, VlcPlayerViewRef, -} from "@/modules/vlc-player/src/VlcPlayer.types"; +} from "@/modules/VlcPlayer.types"; const downloadProvider = !Platform.isTV ? require("@/providers/DownloadProvider") : null; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index e14c00cd..b947b9a6 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -1,5 +1,5 @@ import { Platform } from "react-native"; -import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings"; +import {ScreenOrientationEnum, useSettings, VideoPlayer} from "@/utils/atoms/settings"; import { BitrateSelector, BITRATES } from "@/components/BitrateSelector"; import { BACKGROUND_FETCH_TASK, @@ -22,6 +22,7 @@ import { ListItem } from "../list/ListItem"; import { useTranslation } from "react-i18next"; import DisabledSetting from "@/components/settings/DisabledSetting"; import Dropdown from "@/components/common/Dropdown"; +import {isNumber} from "lodash"; export const OtherSettings: React.FC = () => { const router = useRouter(); @@ -142,6 +143,36 @@ export const OtherSettings: React.FC = () => { /> + {(Platform.OS === "ios" || Platform.isTVOS)&& ( + + t(`home.settings.other.video_players.${VideoPlayer[item]}`)} + title={ + + + {t(`home.settings.other.video_players.${VideoPlayer[settings.defaultPlayer]}`)} + + + + } + label={t("home.settings.other.orientation")} + onSelected={(defaultPlayer) => + updateSettings({ defaultPlayer }) + } + /> + + )} + = ({ )} - {!Platform.isTV && ( + {!Platform.isTV && settings.defaultPlayer == VideoPlayer.VLC_4 && ( ) => void; } -const NativeViewManager = requireNativeViewManager("VlcPlayer"); +const VLCViewManager = requireNativeViewManager("VlcPlayer"); +const VLC3ViewManager = requireNativeViewManager("VlcPlayer3"); // Create a forwarded ref version of the native view const NativeView = React.forwardRef( - (props, ref) => + (props, ref) => { + const [settings] = useSettings(); + + if (Platform.OS === "ios" || Platform.isTVOS) { + if (settings.defaultPlayer == VideoPlayer.VLC_3) { + console.log("[Apple] Using Vlc Player 3") + return + } + } + console.log("Using default Vlc Player") + return + } ); const VlcPlayerView = React.forwardRef( diff --git a/modules/index.ts b/modules/index.ts new file mode 100644 index 00000000..397fc0eb --- /dev/null +++ b/modules/index.ts @@ -0,0 +1,27 @@ +import VlcPlayerView from "./VlcPlayerView"; +import { + PlaybackStatePayload, + ProgressUpdatePayload, + VideoLoadStartPayload, + VideoStateChangePayload, + VideoProgressPayload, + VlcPlayerSource, + TrackInfo, + ChapterInfo, + VlcPlayerViewProps, + VlcPlayerViewRef, +} from "./VlcPlayer.types"; + +export { + VlcPlayerView, + VlcPlayerViewProps, + VlcPlayerViewRef, + PlaybackStatePayload, + ProgressUpdatePayload, + VideoLoadStartPayload, + VideoStateChangePayload, + VideoProgressPayload, + VlcPlayerSource, + TrackInfo, + ChapterInfo, +}; diff --git a/modules/vlc-player-3/expo-module.config.json b/modules/vlc-player-3/expo-module.config.json new file mode 100644 index 00000000..1e6766d7 --- /dev/null +++ b/modules/vlc-player-3/expo-module.config.json @@ -0,0 +1,6 @@ +{ + "platforms": ["ios", "tvos"], + "ios": { + "modules": ["VlcPlayer3Module"] + } +} diff --git a/modules/vlc-player-3/ios/VlcPlayer3.podspec b/modules/vlc-player-3/ios/VlcPlayer3.podspec new file mode 100644 index 00000000..15274a12 --- /dev/null +++ b/modules/vlc-player-3/ios/VlcPlayer3.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'VlcPlayer3' + s.version = '3.6.1b1' + s.summary = 'A sample project summary' + s.description = 'A sample project description' + s.author = '' + s.homepage = 'https://docs.expo.dev/modules/' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.ios.dependency 'MobileVLCKit', s.version + s.tvos.dependency 'TVVLCKit', s.version + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/vlc-player-3/ios/VlcPlayer3Module.swift b/modules/vlc-player-3/ios/VlcPlayer3Module.swift new file mode 100644 index 00000000..c0e32606 --- /dev/null +++ b/modules/vlc-player-3/ios/VlcPlayer3Module.swift @@ -0,0 +1,71 @@ +import ExpoModulesCore + +public class VlcPlayer3Module: Module { + public func definition() -> ModuleDefinition { + Name("VlcPlayer3") + View(VlcPlayer3View.self) { + Prop("source") { (view: VlcPlayer3View, source: [String: Any]) in + view.setSource(source) + } + + Prop("paused") { (view: VlcPlayer3View, paused: Bool) in + if paused { + view.pause() + } else { + view.play() + } + } + + Events( + "onPlaybackStateChanged", + "onVideoStateChange", + "onVideoLoadStart", + "onVideoLoadEnd", + "onVideoProgress", + "onVideoError", + "onPipStarted" + ) + + AsyncFunction("startPictureInPicture") { (view: VlcPlayer3View) in + view.startPictureInPicture() + } + + AsyncFunction("play") { (view: VlcPlayer3View) in + view.play() + } + + AsyncFunction("pause") { (view: VlcPlayer3View) in + view.pause() + } + + AsyncFunction("stop") { (view: VlcPlayer3View) in + view.stop() + } + + AsyncFunction("seekTo") { (view: VlcPlayer3View, time: Int32) in + view.seekTo(time) + } + + AsyncFunction("setAudioTrack") { (view: VlcPlayer3View, trackIndex: Int) in + view.setAudioTrack(trackIndex) + } + + AsyncFunction("getAudioTracks") { (view: VlcPlayer3View) -> [[String: Any]]? in + return view.getAudioTracks() + } + + AsyncFunction("setSubtitleTrack") { (view: VlcPlayer3View, trackIndex: Int) in + view.setSubtitleTrack(trackIndex) + } + + AsyncFunction("getSubtitleTracks") { (view: VlcPlayer3View) -> [[String: Any]]? in + return view.getSubtitleTracks() + } + + AsyncFunction("setSubtitleURL") { + (view: VlcPlayer3View, url: String, name: String) in + view.setSubtitleURL(url, name: name) + } + } + } +} diff --git a/modules/vlc-player-3/ios/VlcPlayer3View.swift b/modules/vlc-player-3/ios/VlcPlayer3View.swift new file mode 100644 index 00000000..b9189f9f --- /dev/null +++ b/modules/vlc-player-3/ios/VlcPlayer3View.swift @@ -0,0 +1,388 @@ +import ExpoModulesCore +#if os(tvOS) +import TVVLCKit +#else +import MobileVLCKit +#endif +import UIKit + +class VlcPlayer3View: ExpoView { + private var mediaPlayer: VLCMediaPlayer? + private var videoView: UIView? + private var progressUpdateInterval: TimeInterval = 1.0 // Update interval set to 1 second + private var isPaused: Bool = false + private var currentGeometryCString: [CChar]? + private var lastReportedState: VLCMediaPlayerState? + private var lastReportedIsPlaying: Bool? + private var customSubtitles: [(internalName: String, originalName: String)] = [] + private var startPosition: Int32 = 0 + private var isMediaReady: Bool = false + private var externalTrack: [String: String]? + private var progressTimer: DispatchSourceTimer? + private var isStopping: Bool = false // Define isStopping here + private var lastProgressCall = Date().timeIntervalSince1970 + var hasSource = false + + // MARK: - Initialization + + required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + setupView() + setupNotifications() + } + + // MARK: - Setup + + private func setupView() { + DispatchQueue.main.async { + self.backgroundColor = .black + self.videoView = UIView() + self.videoView?.translatesAutoresizingMaskIntoConstraints = false + + if let videoView = self.videoView { + self.addSubview(videoView) + NSLayoutConstraint.activate([ + videoView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + videoView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + videoView.topAnchor.constraint(equalTo: self.topAnchor), + videoView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + ]) + } + } + } + + private func setupNotifications() { + NotificationCenter.default.addObserver( + self, selector: #selector(applicationWillResignActive), + name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.addObserver( + self, selector: #selector(applicationDidBecomeActive), + name: UIApplication.didBecomeActiveNotification, object: nil) + } + + // MARK: - Public Methods + func startPictureInPicture() { } + + @objc func play() { + self.mediaPlayer?.play() + self.isPaused = false + print("Play") + } + + @objc func pause() { + self.mediaPlayer?.pause() + self.isPaused = true + } + + @objc func seekTo(_ time: Int32) { + guard let player = self.mediaPlayer else { return } + + let wasPlaying = player.isPlaying + if wasPlaying { + self.pause() + } + + if let duration = player.media?.length.intValue { + print("Seeking to time: \(time) Video Duration \(duration)") + + // If the specified time is greater than the duration, seek to the end + let seekTime = time > duration ? duration - 1000 : time + player.time = VLCTime(int: seekTime) + + if wasPlaying { + self.play() + } + self.updatePlayerState() + } else { + print("Error: Unable to retrieve video duration") + } + } + + @objc func setSource(_ source: [String: Any]) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + if self.hasSource { + return + } + + let mediaOptions = source["mediaOptions"] as? [String: Any] ?? [:] + self.externalTrack = source["externalTrack"] as? [String: String] + var initOptions = source["initOptions"] as? [Any] ?? [] + self.startPosition = source["startPosition"] as? Int32 ?? 0 + initOptions.append("--start-time=\(self.startPosition)") + + guard let uri = source["uri"] as? String, !uri.isEmpty else { + print("Error: Invalid or empty URI") + self.onVideoError?(["error": "Invalid or empty URI"]) + return + } + + let autoplay = source["autoplay"] as? Bool ?? false + let isNetwork = source["isNetwork"] as? Bool ?? false + + self.onVideoLoadStart?(["target": self.reactTag ?? NSNull()]) + self.mediaPlayer = VLCMediaPlayer(options: initOptions) + self.mediaPlayer?.delegate = self + self.mediaPlayer?.drawable = self.videoView + self.mediaPlayer?.scaleFactor = 0 + + let media: VLCMedia + if isNetwork { + print("Loading network file: \(uri)") + media = VLCMedia(url: URL(string: uri)!) + } else { + print("Loading local file: \(uri)") + if uri.starts(with: "file://"), let url = URL(string: uri) { + media = VLCMedia(url: url) + } else { + media = VLCMedia(path: uri) + } + } + + print("Debug: Media options: \(mediaOptions)") + media.addOptions(mediaOptions) + + self.mediaPlayer?.media = media + self.hasSource = true + + if autoplay { + print("Playing...") + self.play() + } + } + } + + @objc func setAudioTrack(_ trackIndex: Int) { + self.mediaPlayer?.currentAudioTrackIndex = Int32(trackIndex) + } + + @objc func getAudioTracks() -> [[String: Any]]? { + guard let trackNames = mediaPlayer?.audioTrackNames, + let trackIndexes = mediaPlayer?.audioTrackIndexes + else { + return nil + } + + return zip(trackNames, trackIndexes).map { name, index in + return ["name": name, "index": index] + } + } + + @objc func setSubtitleTrack(_ trackIndex: Int) { + print("Debug: Attempting to set subtitle track to index: \(trackIndex)") + self.mediaPlayer?.currentVideoSubTitleIndex = Int32(trackIndex) + print( + "Debug: Current subtitle track index after setting: \(self.mediaPlayer?.currentVideoSubTitleIndex ?? -1)" + ) + } + + @objc func setSubtitleURL(_ subtitleURL: String, name: String) { + guard let url = URL(string: subtitleURL) else { + print("Error: Invalid subtitle URL") + return + } + + let result = self.mediaPlayer?.addPlaybackSlave(url, type: .subtitle, enforce: true) + if let result = result { + let internalName = "Track \(self.customSubtitles.count + 1)" + print("Subtitle added with result: \(result) \(internalName)") + self.customSubtitles.append((internalName: internalName, originalName: name)) + } else { + print("Failed to add subtitle") + } + } + + @objc func getSubtitleTracks() -> [[String: Any]]? { + guard let mediaPlayer = self.mediaPlayer else { + return nil + } + + let count = mediaPlayer.numberOfSubtitlesTracks + print("Debug: Number of subtitle tracks: \(count)") + + guard count > 0 else { + return nil + } + + var tracks: [[String: Any]] = [] + + if let names = mediaPlayer.videoSubTitlesNames as? [String], + let indexes = mediaPlayer.videoSubTitlesIndexes as? [NSNumber] + { + for (index, name) in zip(indexes, names) { + if let customSubtitle = customSubtitles.first(where: { $0.internalName == name }) { + tracks.append(["name": customSubtitle.originalName, "index": index.intValue]) + } else { + tracks.append(["name": name, "index": index.intValue]) + } + } + } + + print("Debug: Subtitle tracks: \(tracks)") + return tracks + } + + @objc func stop(completion: (() -> Void)? = nil) { + guard !isStopping else { + completion?() + return + } + isStopping = true + + // If we're not on the main thread, dispatch to main thread + if !Thread.isMainThread { + DispatchQueue.main.async { [weak self] in + self?.performStop(completion: completion) + } + } else { + performStop(completion: completion) + } + } + + // MARK: - Private Methods + + @objc private func applicationWillResignActive() { + + } + + @objc private func applicationDidBecomeActive() { + + } + + private func performStop(completion: (() -> Void)? = nil) { + // Stop the media player + mediaPlayer?.stop() + + // Remove observer + NotificationCenter.default.removeObserver(self) + + // Clear the video view + videoView?.removeFromSuperview() + videoView = nil + + // Release the media player + mediaPlayer?.delegate = nil + mediaPlayer = nil + + isStopping = false + completion?() + } + + private func updateVideoProgress() { + guard let player = self.mediaPlayer else { return } + + let currentTimeMs = player.time.intValue + let durationMs = player.media?.length.intValue ?? 0 + + print("Debug: Current time: \(currentTimeMs)") + if currentTimeMs >= 0 && currentTimeMs < durationMs { + if player.isPlaying && !self.isMediaReady { + self.isMediaReady = true + // Set external track subtitle when starting. + if let externalTrack = self.externalTrack { + if let name = externalTrack["name"], !name.isEmpty { + let deliveryUrl = externalTrack["DeliveryUrl"] ?? "" + self.setSubtitleURL(deliveryUrl, name: name) + } + } + } + self.onVideoProgress?([ + "currentTime": currentTimeMs, + "duration": durationMs, + ]) + } + } + + // MARK: - Expo Events + + @objc var onPlaybackStateChanged: RCTDirectEventBlock? + @objc var onVideoLoadStart: RCTDirectEventBlock? + @objc var onVideoStateChange: RCTDirectEventBlock? + @objc var onVideoProgress: RCTDirectEventBlock? + @objc var onVideoLoadEnd: RCTDirectEventBlock? + @objc var onVideoError: RCTDirectEventBlock? + @objc var onPipStarted: RCTDirectEventBlock? + + // MARK: - Deinitialization + + deinit { + performStop() + } +} + +extension VlcPlayer3View: VLCMediaPlayerDelegate { + func mediaPlayerTimeChanged(_ aNotification: Notification) { + // self?.updateVideoProgress() + let timeNow = Date().timeIntervalSince1970 + if timeNow - lastProgressCall >= 1 { + lastProgressCall = timeNow + updateVideoProgress() + } + } + + func mediaPlayerStateChanged(_ aNotification: Notification) { + self.updatePlayerState() + } + + private func updatePlayerState() { + guard let player = self.mediaPlayer else { return } + let currentState = player.state + + var stateInfo: [String: Any] = [ + "target": self.reactTag ?? NSNull(), + "currentTime": player.time.intValue, + "duration": player.media?.length.intValue ?? 0, + "error": false, + ] + + if player.isPlaying { + stateInfo["isPlaying"] = true + stateInfo["isBuffering"] = false + stateInfo["state"] = "Playing" + } else { + stateInfo["isPlaying"] = false + stateInfo["state"] = "Paused" + } + + if player.state == VLCMediaPlayerState.buffering { + stateInfo["isBuffering"] = true + stateInfo["state"] = "Buffering" + } else if player.state == VLCMediaPlayerState.error { + print("player.state ~ error") + stateInfo["state"] = "Error" + self.onVideoLoadEnd?(stateInfo) + } else if player.state == VLCMediaPlayerState.opening { + print("player.state ~ opening") + stateInfo["state"] = "Opening" + } + + if self.lastReportedState != currentState + || self.lastReportedIsPlaying != player.isPlaying + { + self.lastReportedState = currentState + self.lastReportedIsPlaying = player.isPlaying + self.onVideoStateChange?(stateInfo) + } + + } +} + +extension VlcPlayer3View: VLCMediaDelegate { + // Implement VLCMediaDelegate methods if needed +} + +extension VLCMediaPlayerState { + var description: String { + switch self { + case .opening: return "Opening" + case .buffering: return "Buffering" + case .playing: return "Playing" + case .paused: return "Paused" + case .stopped: return "Stopped" + case .ended: return "Ended" + case .error: return "Error" + case .esAdded: return "ESAdded" + @unknown default: return "Unknown" + } + } +} diff --git a/modules/vlc-player-3/src/VlcPlayer3Module.ts b/modules/vlc-player-3/src/VlcPlayer3Module.ts new file mode 100644 index 00000000..b292aaff --- /dev/null +++ b/modules/vlc-player-3/src/VlcPlayer3Module.ts @@ -0,0 +1,5 @@ +import { requireNativeModule } from 'expo-modules-core'; + +// It loads the native module object from the JSI or falls back to +// the bridge module (from NativeModulesProxy) if the remote debugger is on. +export default requireNativeModule('VlcPlayer3'); diff --git a/modules/vlc-player/android/.gradle/8.9/checksums/checksums.lock b/modules/vlc-player/android/.gradle/8.9/checksums/checksums.lock deleted file mode 100644 index 52a7f0f4db1362d9d70601745a03ec2299581cfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 TcmZQxc(L*Ap=GabGC% void -): EventSubscription { - return emitter.addListener( - "onPlaybackStateChanged", - listener - ); -} - -export function addVideoLoadStartListener( - listener: (event: VideoLoadStartPayload) => void -): EventSubscription { - return emitter.addListener( - "onVideoLoadStart", - listener - ); -} - -export function addVideoStateChangeListener( - listener: (event: VideoStateChangePayload) => void -): EventSubscription { - return emitter.addListener( - "onVideoStateChange", - listener - ); -} - -export function addVideoProgressListener( - listener: (event: VideoProgressPayload) => void -): EventSubscription { - return emitter.addListener("onVideoProgress", listener); -} - -export { - VlcPlayerView, - VlcPlayerViewProps, - VlcPlayerViewRef, - PlaybackStatePayload, - ProgressUpdatePayload, - VideoLoadStartPayload, - VideoStateChangePayload, - VideoProgressPayload, - VlcPlayerSource, - TrackInfo, - ChapterInfo, -}; diff --git a/modules/vlc-player/ios/VlcPlayer.podspec b/modules/vlc-player/ios/VlcPlayer.podspec index 89e84814..46dbafd1 100644 --- a/modules/vlc-player/ios/VlcPlayer.podspec +++ b/modules/vlc-player/ios/VlcPlayer.podspec @@ -19,6 +19,5 @@ Pod::Spec.new do |s| 'DEFINES_MODULE' => 'YES', 'SWIFT_COMPILATION_MODE' => 'wholemodule' } - - s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" + s.source_files = "*.{h,m,mm,swift,hpp,cpp}" end diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift index c40b5108..f02478d2 100644 --- a/modules/vlc-player/ios/VlcPlayerView.swift +++ b/modules/vlc-player/ios/VlcPlayerView.swift @@ -3,7 +3,6 @@ import UIKit import VLCKit import os - public class VLCPlayerView: UIView { func setupView(parent: UIView) { self.backgroundColor = .black diff --git a/translations/de.json b/translations/de.json index 962e10d7..cca7b183 100644 --- a/translations/de.json +++ b/translations/de.json @@ -128,7 +128,12 @@ "OTHER": "Andere", "UNKNOWN": "Unbekannt" }, - "safe_area_in_controls": "Sicherer Bereich in den Steuerungen", + "safe_area_in_controls": "Sicherer Bereich in den Steuerungen", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen", "hide_libraries": "Bibliotheken ausblenden", "select_liraries_you_want_to_hide": "Wähl die Bibliotheken aus, die du im Bibliothekstab und auf der Startseite ausblenden möchtest.", diff --git a/translations/en.json b/translations/en.json index 5ba25ec9..40594300 100644 --- a/translations/en.json +++ b/translations/en.json @@ -129,11 +129,16 @@ "UNKNOWN": "Unknown" }, "safe_area_in_controls": "Safe area in controls", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Show Custom Menu Links", "hide_libraries": "Hide Libraries", "select_liraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.", "disable_haptic_feedback": "Disable Haptic Feedback", - "default_quality": "Default quality" + "default_quality": "Default quality", }, "downloads": { "downloads_title": "Downloads", diff --git a/translations/es.json b/translations/es.json index 9004c2f8..9a2962a3 100644 --- a/translations/es.json +++ b/translations/es.json @@ -128,7 +128,12 @@ "OTHER": "Otra", "UNKNOWN": "Desconocida" }, - "safe_area_in_controls": "Área segura en controles", + "safe_area_in_controls": "Área segura en controles", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Mostrar enlaces de menú personalizados", "hide_libraries": "Ocultar bibliotecas", "select_liraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.", diff --git a/translations/fr.json b/translations/fr.json index 7347d274..719b5fae 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -128,7 +128,12 @@ "OTHER": "Autre", "UNKNOWN": "Inconnu" }, - "safe_area_in_controls": "Zone de sécurité dans les contrôles", + "safe_area_in_controls": "Zone de sécurité dans les contrôles", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Afficher les liens personnalisés", "hide_libraries": "Cacher des bibliothèques", "select_liraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l’onglet Bibliothèque et les sections de la page d’accueil.", diff --git a/translations/it.json b/translations/it.json index 60a95bb1..fc713f8e 100644 --- a/translations/it.json +++ b/translations/it.json @@ -128,7 +128,12 @@ "OTHER": "Altro", "UNKNOWN": "Sconosciuto" }, - "safe_area_in_controls": "Area sicura per i controlli", + "safe_area_in_controls": "Area sicura per i controlli", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Mostra i link del menu personalizzato", "hide_libraries": "Nascondi Librerie", "select_liraries_you_want_to_hide": "Selezionate le librerie che volete nascondere dalla scheda Libreria e dalle sezioni della pagina iniziale.", diff --git a/translations/ja.json b/translations/ja.json index acd99ff2..085b6c3d 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -128,7 +128,12 @@ "OTHER": "その他", "UNKNOWN": "不明" }, - "safe_area_in_controls": "コントロールの安全エリア", + "safe_area_in_controls": "コントロールの安全エリア", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "カスタムメニューのリンクを表示", "hide_libraries": "ライブラリを非表示", "select_liraries_you_want_to_hide": "ライブラリタブとホームページセクションから非表示にするライブラリを選択します。", diff --git a/translations/nl.json b/translations/nl.json index d2129263..0e44a305 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -128,7 +128,12 @@ "OTHER": "Andere", "UNKNOWN": "Onbekend" }, - "safe_area_in_controls": "Veilig gebied in bedieningen", + "safe_area_in_controls": "Veilig gebied in bedieningen", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Aangepaste menulinks tonen", "hide_libraries": "Verberg Bibliotheken", "select_liraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.", diff --git a/translations/tr.json b/translations/tr.json index bdc140c8..a9c65b02 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -128,7 +128,12 @@ "OTHER": "Diğer", "UNKNOWN": "Bilinmeyen" }, - "safe_area_in_controls": "Kontrollerde Güvenli Alan", + "safe_area_in_controls": "Kontrollerde Güvenli Alan", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "Özel Menü Bağlantılarını Göster", "hide_libraries": "Kütüphaneleri Gizle", "select_liraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.", diff --git a/translations/zh-CN.json b/translations/zh-CN.json index 94c1dad5..2fb3abf9 100644 --- a/translations/zh-CN.json +++ b/translations/zh-CN.json @@ -128,7 +128,12 @@ "OTHER": "其他", "UNKNOWN": "未知" }, - "safe_area_in_controls": "控制中的安全区域", + "safe_area_in_controls": "控制中的安全区域", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "显示自定义菜单链接", "hide_libraries": "隐藏媒体库", "select_liraries_you_want_to_hide": "选择您想从媒体库页面和主页隐藏的媒体库。", diff --git a/translations/zh-TW.json b/translations/zh-TW.json index 2dbd287e..3f127a6b 100644 --- a/translations/zh-TW.json +++ b/translations/zh-TW.json @@ -128,7 +128,12 @@ "OTHER": "其他", "UNKNOWN": "未知" }, - "safe_area_in_controls": "控制中的安全區域", + "safe_area_in_controls": "控制中的安全區域", + "video_player": "Video player", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Experimental + PiP)" + }, "show_custom_menu_links": "顯示自定義菜單鏈接", "hide_libraries": "隱藏媒體庫", "select_liraries_you_want_to_hide": "選擇您想從媒體庫頁面和主頁隱藏的媒體庫。", diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index 2426be4f..a1ba7d0b 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -14,6 +14,7 @@ import { import { Bitrate, BITRATES } from "@/components/BitrateSelector"; import { apiAtom } from "@/providers/JellyfinProvider"; import { writeInfoLog } from "@/utils/log"; +import {Video} from "@/utils/jellyseerr/server/models/Movie"; const STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004"; const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS"; @@ -112,6 +113,12 @@ export type HomeSectionNextUpResolver = { enableRewatching?: boolean; }; +export enum VideoPlayer { + // NATIVE, //todo: changes will make this a lot more easier to implement if we want. delete if not wanted + VLC_3, + VLC_4 +} + export type Settings = { home?: Home | null; autoRotate?: boolean; @@ -146,6 +153,7 @@ export type Settings = { jellyseerrServerUrl?: string; hiddenLibraries?: string[]; enableH265ForChromecast: boolean; + defaultPlayer: VideoPlayer; }; export interface Lockable { @@ -200,6 +208,7 @@ const defaultValues: Settings = { jellyseerrServerUrl: undefined, hiddenLibraries: [], enableH265ForChromecast: false, + defaultPlayer: VideoPlayer.VLC_3, // ios only setting. does not matter what this is for android }; const loadSettings = (): Partial => {