From d9fde3ba7934f88fcf58b0bb81b1e1fc03cfbd99 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sat, 19 Oct 2024 21:20:11 +0200 Subject: [PATCH] wip --- .../(home,libraries,search)/items/page.tsx | 2 +- app/(auth)/vlc-player.tsx | 2 + components/ItemContent.tsx | 62 +++-- components/MediaSourceSelector.tsx | 2 +- hooks/useDefaultPlaySettings.ts | 6 +- hooks/useImageColors.ts | 1 - modules/vlc-player/ios/VlcPlayer.podspec | 2 +- modules/vlc-player/ios/VlcPlayerModule.swift | 30 +- modules/vlc-player/ios/VlcPlayerView.swift | 261 ++++++++++-------- 9 files changed, 211 insertions(+), 157 deletions(-) diff --git a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx b/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx index 5f4e8186..571ff994 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/items/page.tsx @@ -34,7 +34,7 @@ const Page: React.FC = () => { return res.data; }, - enabled: !!id && !!api, + enabled: !!id && !!api || !!user, staleTime: 0, }); diff --git a/app/(auth)/vlc-player.tsx b/app/(auth)/vlc-player.tsx index 653eae39..e3224266 100644 --- a/app/(auth)/vlc-player.tsx +++ b/app/(auth)/vlc-player.tsx @@ -230,6 +230,8 @@ export default function page() { const { currentTime, isPlaying } = data.nativeEvent; + console.log("onProgress", currentTime); + progress.value = currentTime; const currentTimeInTicks = msToTicks(currentTime); diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 13d2f019..75d90804 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -52,6 +52,10 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( const [loadingLogo, setLoadingLogo] = useState(true); const [headerHeight, setHeaderHeight] = useState(350); + const [selectedOptions, setSelectedOptions] = useState< + SelectedOptions | undefined + >(undefined); + const { defaultAudioIndex, defaultBitrate, @@ -59,12 +63,19 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( defaultSubtitleIndex, } = useDefaultPlaySettings(item, settings); - const [selectedOptions, setSelectedOptions] = useState({ - bitrate: defaultBitrate, - mediaSource: defaultMediaSource, - audioIndex: defaultAudioIndex, - subtitleIndex: defaultSubtitleIndex || -1, - }); + useEffect(() => { + setSelectedOptions(() => ({ + bitrate: defaultBitrate, + mediaSource: defaultMediaSource, + subtitleIndex: defaultSubtitleIndex ?? -1, + audioIndex: defaultAudioIndex, + })); + }, [ + defaultAudioIndex, + defaultBitrate, + defaultSubtitleIndex, + defaultMediaSource, + ]); useEffect(() => { navigation.setOptions({ @@ -96,6 +107,8 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( return Boolean(logoUrl && loadingLogo); }, [loadingLogo, logoUrl]); + if (!selectedOptions) return null; + return ( = React.memo( - setSelectedOptions((prev) => ({ ...prev, bitrate: val })) + setSelectedOptions( + (prev) => prev && { ...prev, bitrate: val } + ) } selected={selectedOptions.bitrate} /> @@ -156,10 +171,13 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( className="mr-1" item={item} onChange={(val) => - setSelectedOptions((prev) => ({ - ...prev, - mediaSource: val, - })) + setSelectedOptions( + (prev) => + prev && { + ...prev, + mediaSource: val, + } + ) } selected={selectedOptions.mediaSource} /> @@ -167,20 +185,26 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( className="mr-1" source={selectedOptions.mediaSource} onChange={(val) => - setSelectedOptions((prev) => ({ - ...prev, - audioIndex: val, - })) + setSelectedOptions( + (prev) => + prev && { + ...prev, + audioIndex: val, + } + ) } selected={selectedOptions.audioIndex} /> - setSelectedOptions((prev) => ({ - ...prev, - subtitleIndex: val, - })) + setSelectedOptions( + (prev) => + prev && { + ...prev, + subtitleIndex: val, + } + ) } selected={selectedOptions.subtitleIndex} /> diff --git a/components/MediaSourceSelector.tsx b/components/MediaSourceSelector.tsx index 91e6d91a..34f02fd9 100644 --- a/components/MediaSourceSelector.tsx +++ b/components/MediaSourceSelector.tsx @@ -26,7 +26,7 @@ export const MediaSourceSelector: React.FC = ({ item.MediaSources?.find((x) => x.Id === selected?.Id)?.MediaStreams?.find( (x) => x.Type === "Video" )?.DisplayTitle || "", - [item.MediaSources, selected] + [item, selected] ); return ( diff --git a/hooks/useDefaultPlaySettings.ts b/hooks/useDefaultPlaySettings.ts index e127fa48..d1da0dae 100644 --- a/hooks/useDefaultPlaySettings.ts +++ b/hooks/useDefaultPlaySettings.ts @@ -42,7 +42,11 @@ const useDefaultPlaySettings = ( defaultMediaSource: mediaSource || undefined, defaultBitrate: bitrate || undefined, }; - }, [item, settings]); + }, [ + item.MediaSources, + settings?.defaultAudioLanguage, + settings?.defaultSubtitleLanguage, + ]); return playSettings; }; diff --git a/hooks/useImageColors.ts b/hooks/useImageColors.ts index 57fb6c80..c7250c86 100644 --- a/hooks/useImageColors.ts +++ b/hooks/useImageColors.ts @@ -54,7 +54,6 @@ export const useImageColors = ({ // If colors are cached, use them and exit if (_primary && _text) { - console.info("useImageColors ~ Using cached colors for performance."); setPrimaryColor({ primary: _primary, text: _text, diff --git a/modules/vlc-player/ios/VlcPlayer.podspec b/modules/vlc-player/ios/VlcPlayer.podspec index d3222c6a..642026ae 100644 --- a/modules/vlc-player/ios/VlcPlayer.podspec +++ b/modules/vlc-player/ios/VlcPlayer.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.static_framework = true s.dependency 'ExpoModulesCore' - s.dependency 'MobileVLCKit' + s.dependency 'MobileVLCKit', '~> 3.6.1b1' # Swift/Objective-C compatibility s.pod_target_xcconfig = { diff --git a/modules/vlc-player/ios/VlcPlayerModule.swift b/modules/vlc-player/ios/VlcPlayerModule.swift index c5f4484c..992ecd0c 100644 --- a/modules/vlc-player/ios/VlcPlayerModule.swift +++ b/modules/vlc-player/ios/VlcPlayerModule.swift @@ -16,17 +16,17 @@ public class VlcPlayerModule: Module { } } - Prop("muted") { (view: VlcPlayerView, muted: Bool) in - view.setMuted(muted) - } +// Prop("muted") { (view: VlcPlayerView, muted: Bool) in +// view.setMuted(muted) +// } - Prop("volume") { (view: VlcPlayerView, volume: Int) in - view.setVolume(volume) - } +// Prop("volume") { (view: VlcPlayerView, volume: Int) in +// view.setVolume(volume) +// } - Prop("videoAspectRatio") { (view: VlcPlayerView, ratio: String) in - view.setVideoAspectRatio(ratio) - } +// Prop("videoAspectRatio") { (view: VlcPlayerView, ratio: String) in +// view.setVideoAspectRatio(ratio) +// } Events( "onPlaybackStateChanged", @@ -69,13 +69,13 @@ public class VlcPlayerModule: Module { return view.getSubtitleTracks() } - AsyncFunction("setVideoCropGeometry") { (view: VlcPlayerView, geometry: String?) in - view.setVideoCropGeometry(geometry) - } +// AsyncFunction("setVideoCropGeometry") { (view: VlcPlayerView, geometry: String?) in +// view.setVideoCropGeometry(geometry) +// } - AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in - return view.getVideoCropGeometry() - } +// AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in +// return view.getVideoCropGeometry() +// } AsyncFunction("setSubtitleURL") { (view: VlcPlayerView, url: String, name: String) in diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift index ff0ff7a8..4dd4d7fd 100644 --- a/modules/vlc-player/ios/VlcPlayerView.swift +++ b/modules/vlc-player/ios/VlcPlayerView.swift @@ -18,7 +18,7 @@ class VlcPlayerView: ExpoView { required init(appContext: AppContext? = nil) { super.init(appContext: appContext) setupView() - setupNotifications() + // setupNotifications() } // MARK: - Setup @@ -68,6 +68,7 @@ class VlcPlayerView: ExpoView { guard let self = self else { return } self.mediaPlayer?.play() self.isPaused = false + print("Play") } } @@ -143,6 +144,7 @@ class VlcPlayerView: ExpoView { media = VLCMedia(url: url) } else { print("Error: Invalid local file URL") + self.onVideoError?(["error": "Invalid local file URL"]) return } } else { @@ -155,13 +157,13 @@ class VlcPlayerView: ExpoView { media.addOptions(subtitleOptions) print("Debug: Applied subtitle options: \(subtitleOptions)") - // Apply any additional media options - if let mediaOptions = mediaOptions { - media.addOptions(mediaOptions) - print("Debug: Applied additional media options: \(mediaOptions)") - } else { - print("Debug: No additional media options provided") - } + // // Apply any additional media options + // if let mediaOptions = mediaOptions { + // media.addOptions(mediaOptions) + // print("Debug: Applied additional media options: \(mediaOptions)") + // } else { + // print("Debug: No additional media options provided") + // } // Apply subtitle options let subtitleTrackIndex = source["subtitleTrackIndex"] as? Int ?? -1 @@ -177,19 +179,30 @@ class VlcPlayerView: ExpoView { self.mediaPlayer?.media = media if startPosition > 0 { - // Wait for the media to be ready before setting the start position - NotificationCenter.default.addObserver( - forName: NSNotification.Name(rawValue: VLCMediaPlayerStateChanged), object: nil, - queue: nil - ) { [weak self] notification in - guard let self = self, let player = self.mediaPlayer, - player.isPlaying == false - else { return } + // Create a closure to set the start position + let setStartPosition = { [weak self] in + self?.mediaPlayer?.time = VLCTime(int: startPosition) + } - self.mediaPlayer?.time = VLCTime(int: startPosition) - NotificationCenter.default.removeObserver( - self, name: NSNotification.Name(rawValue: VLCMediaPlayerStateChanged), - object: nil) + // Check if the media is already ready + if self.isMediaReady { + setStartPosition() + } else { + // If not ready, set up an observer to wait for the media to be ready + NotificationCenter.default.addObserver( + forName: .VLCMediaPlayerStateChanged, object: self.mediaPlayer, queue: .main + ) { [weak self] notification in + guard let self = self, let player = self.mediaPlayer else { return } + + if player.state == .playing || player.state == .paused { + // Media is ready, set the start position + setStartPosition() + + // Remove the observer + NotificationCenter.default.removeObserver( + self, name: .VLCMediaPlayerStateChanged, object: player) + } + } } } @@ -200,25 +213,28 @@ class VlcPlayerView: ExpoView { } } - @objc func setMuted(_ muted: Bool) { - DispatchQueue.main.async { - self.mediaPlayer?.audio?.isMuted = muted - } - } + // TODO + // @objc func setMuted(_ muted: Bool) { + // DispatchQueue.main.async { + // self.mediaPlayer?.audio?.isMuted = muted + // } + // } - @objc func setVolume(_ volume: Int) { - DispatchQueue.main.async { - self.mediaPlayer?.audio?.volume = Int32(volume) - } - } + // TODO + // @objc func setVolume(_ volume: Int) { + // DispatchQueue.main.async { + // self.mediaPlayer?.audio?.volume = Int32(volume) + // } + // } - @objc func setVideoAspectRatio(_ ratio: String) { - DispatchQueue.main.async { - ratio.withCString { cString in - self.mediaPlayer?.videoAspectRatio = UnsafeMutablePointer(mutating: cString) - } - } - } + // TODO + // @objc func setVideoAspectRatio(_ ratio: String) { + // DispatchQueue.main.async { + // ratio.withCString { cString in + // self.mediaPlayer?.videoAspectRatio = UnsafeMutablePointer(mutating: cString) + // } + // } + // } @objc func setAudioTrack(_ trackIndex: Int) { DispatchQueue.main.async { @@ -373,88 +389,97 @@ class VlcPlayerView: ExpoView { // } // } - @objc func setSubtitleDelay(_ delay: Int) { - DispatchQueue.main.async { - self.mediaPlayer?.currentVideoSubTitleDelay = NSInteger(delay) - } - } + // TODO + // @objc func setSubtitleDelay(_ delay: Int) { + // DispatchQueue.main.async { + // self.mediaPlayer?.currentVideoSubTitleDelay = NSInteger(delay) + // } + // } - @objc func setAudioDelay(_ delay: Int) { - DispatchQueue.main.async { - self.mediaPlayer?.currentAudioPlaybackDelay = NSInteger(delay) - } - } + // TODO + // @objc func setAudioDelay(_ delay: Int) { + // DispatchQueue.main.async { + // self.mediaPlayer?.currentAudioPlaybackDelay = NSInteger(delay) + // } + // } - @objc func takeSnapshot(_ path: String, width: Int, height: Int) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.mediaPlayer?.saveVideoSnapshot( - at: path, withWidth: Int32(width), andHeight: Int32(height)) - } - } + // TODO + // @objc func takeSnapshot(_ path: String, width: Int, height: Int) { + // DispatchQueue.main.async { [weak self] in + // guard let self = self else { return } + // self.mediaPlayer?.saveVideoSnapshot( + // at: path, withWidth: Int32(width), andHeight: Int32(height)) + // } + // } - @objc func setVideoCropGeometry(_ geometry: String?) { - DispatchQueue.main.async { - if let geometry = geometry, !geometry.isEmpty { - self.currentGeometryCString = geometry.cString(using: .utf8) - self.currentGeometryCString?.withUnsafeMutableBufferPointer { buffer in - self.mediaPlayer?.videoCropGeometry = buffer.baseAddress - } - } else { - self.currentGeometryCString = nil - self.mediaPlayer?.videoCropGeometry = nil - } - } - } + // TODO + // @objc func setVideoCropGeometry(_ geometry: String?) { + // DispatchQueue.main.async { + // if let geometry = geometry, !geometry.isEmpty { + // self.currentGeometryCString = geometry.cString(using: .utf8) + // self.currentGeometryCString?.withUnsafeMutableBufferPointer { buffer in + // self.mediaPlayer?.videoCropGeometry = buffer.baseAddress + // } + // } else { + // self.currentGeometryCString = nil + // self.mediaPlayer?.videoCropGeometry = nil + // } + // } + // } - @objc func getVideoCropGeometry() -> String? { - guard let cString = mediaPlayer?.videoCropGeometry else { - return nil - } - return String(cString: cString) - } + // TODO + // @objc func getVideoCropGeometry() -> String? { + // guard let cString = mediaPlayer?.videoCropGeometry else { + // return nil + // } + // return String(cString: cString) + // } - @objc func setRate(_ rate: Float) { - DispatchQueue.main.async { - self.mediaPlayer?.rate = rate - } - } + // TODO + // @objc func setRate(_ rate: Float) { + // DispatchQueue.main.async { + // self.mediaPlayer?.rate = rate + // } + // } - @objc func nextChapter() { - DispatchQueue.main.async { - self.mediaPlayer?.nextChapter() - } - } + // TODO + // @objc func nextChapter() { + // DispatchQueue.main.async { + // self.mediaPlayer?.nextChapter() + // } + // } - @objc func previousChapter() { - DispatchQueue.main.async { - self.mediaPlayer?.previousChapter() - } - } + // TODO + // @objc func previousChapter() { + // DispatchQueue.main.async { + // self.mediaPlayer?.previousChapter() + // } + // } - @objc func getChapters() -> [[String: Any]]? { - guard let currentTitleIndex = mediaPlayer?.currentTitleIndex, - let chapters = mediaPlayer?.chapterDescriptions(ofTitle: currentTitleIndex) - as? [[String: Any]] - else { - return nil - } + // TODO + // @objc func getChapters() -> [[String: Any]]? { + // guard let currentTitleIndex = mediaPlayer?.currentTitleIndex, + // let chapters = mediaPlayer?.chapterDescriptions(ofTitle: currentTitleIndex) + // as? [[String: Any]] + // else { + // return nil + // } - return chapters.compactMap { chapter in - guard let name = chapter[VLCChapterDescriptionName] as? String, - let timeOffset = chapter[VLCChapterDescriptionTimeOffset] as? NSNumber, - let duration = chapter[VLCChapterDescriptionDuration] as? NSNumber - else { - return nil - } + // return chapters.compactMap { chapter in + // guard let name = chapter[VLCChapterDescriptionName] as? String, + // let timeOffset = chapter[VLCChapterDescriptionTimeOffset] as? NSNumber, + // let duration = chapter[VLCChapterDescriptionDuration] as? NSNumber + // else { + // return nil + // } - return [ - "name": name, - "timeOffset": timeOffset.doubleValue, - "duration": duration.doubleValue, - ] - } - } + // return [ + // "name": name, + // "timeOffset": timeOffset.doubleValue, + // "duration": duration.doubleValue, + // ] + // } + // } private var isStopping: Bool = false @@ -641,16 +666,16 @@ extension VlcPlayerView: VLCMediaPlayerDelegate { } extension VlcPlayerView: VLCMediaDelegate { - func mediaMetaDataDidChange(_ aMedia: VLCMedia) { - // Implement if needed - } + // func mediaMetaDataDidChange(_ aMedia: VLCMedia) { + // // Implement if needed + // } - func mediaDidFinishParsing(_ aMedia: VLCMedia) { - DispatchQueue.main.async { - let duration = aMedia.length.intValue - self.onVideoStateChange?(["type": "MediaParsed", "duration": duration]) - } - } + // func mediaDidFinishParsing(_ aMedia: VLCMedia) { + // DispatchQueue.main.async { + // let duration = aMedia.length.intValue + // self.onVideoStateChange?(["type": "MediaParsed", "duration": duration]) + // } + // } } extension VLCMediaPlayerState {