Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions swiftchan/Services/CacheService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,14 @@ final class CacheManager {
}
}

/// Basic validation to ensure cached WebM files are not corrupted.
func isValidWebm(file: URL) -> Bool {
guard let handle = try? FileHandle(forReadingFrom: file) else { return false }
let header = handle.readData(ofLength: 4)
handle.closeFile()
let bytes = [UInt8](header)
// EBML header 0x1A45DFA3
return bytes.count == 4 && bytes[0] == 0x1A && bytes[1] == 0x45 && bytes[2] == 0xDF && bytes[3] == 0xA3
}

}
14 changes: 10 additions & 4 deletions swiftchan/ViewModels/VLCVideoViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ class VLCVideoViewModel {

func download() async throws {
let cacheURL = CacheManager.shared.cacheURL(video.url)
guard !CacheManager.shared.cacheHit(file: cacheURL) else {
markDownloadFinished()
return
if CacheManager.shared.cacheHit(file: cacheURL) {
if CacheManager.shared.isValidWebm(file: cacheURL) {
video = video.with(url: cacheURL)
markDownloadFinished()
return
} else {
try? FileManager.default.removeItem(at: cacheURL)
}
}

debugPrint("Downloading webm: \(cacheURL)")
let (tempURL, _) = try await URLSession.shared.download(from: video.url, progress: video.downloadProgress)
debugPrint("Completed Downloading webm: \(cacheURL)")

guard let cached = CacheManager.shared.cache(tempURL, cacheURL) else { return }
guard let cached = CacheManager.shared.cache(tempURL, cacheURL),
CacheManager.shared.isValidWebm(file: cached) else { return }

// Update URL and mark download complete
video = video.with(url: cached)
Expand Down
16 changes: 4 additions & 12 deletions swiftchan/Views/Media/VLC/VLCContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,33 +58,25 @@ struct VLCContainerView: View {
}
}
}
.onChange(of: vlcVideoViewModel.video.downloadProgress.isFinished) {
if vlcVideoViewModel.video.downloadProgress.isFinished && isSelected {
print("PLaying video 1: \(vlcVideoViewModel.video.url)")
vlcVideoViewModel.play()
}
}
.onChange(of: isSelected) {
if isSelected && vlcVideoViewModel.video.downloadProgress.isFinished {
print("PLaying video 2: \(vlcVideoViewModel.video.url)")
vlcVideoViewModel.play()
} else {
} else if !isSelected {
vlcVideoViewModel.pause()
}
}
.onAppear {
UIApplication.shared.isIdleTimerDisabled = true
if isSelected {
print("PLaying video 3: \(vlcVideoViewModel.video.url)")
vlcVideoViewModel.play()
}
}
.onDisappear {
vlcVideoViewModel.pause()
appState.vlcPlayerControlModifier = nil
}
.task {
try? await vlcVideoViewModel.download()
if isSelected {
vlcVideoViewModel.play()
}
}
}
}
Expand Down
36 changes: 26 additions & 10 deletions swiftchan/Views/Media/VLC/VLCMediaListPlayerUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class VLCMediaListPlayerUIView: UIView, VLCMediaPlayerDelegate {
private weak var delegate: VLCMediaPlayerDelegate?
let mediaListPlayer = VLCMediaListPlayer()
var media: VLCMedia?
private var currentMediaURL: URL?

init(url: URL, frame: CGRect = .zero) {
self.url = url
Expand All @@ -27,18 +28,40 @@ class VLCMediaListPlayerUIView: UIView, VLCMediaPlayerDelegate {

/// Initialize the media with options and setup the media player.
func initialize(url: URL) {
guard currentMediaURL != url || mediaListPlayer.mediaPlayer.media == nil else { return }
resetPlayer()
media = VLCMedia(url: url)
if let media = media {
media.addOption("-vv")
mediaListPlayer.rootMedia = media
mediaListPlayer.mediaPlayer.media = media
mediaListPlayer.mediaPlayer.drawable = self
mediaListPlayer.mediaPlayer.delegate = delegate
mediaListPlayer.repeatMode = .repeatCurrentItem
//mediaListPlayer.mediaPlayer.delegate = self.delegate

#if DEBUG
mediaListPlayer.mediaPlayer.audio?.isMuted = true
#endif
currentMediaURL = url
}
}

/// Safely stop playback and release current media.
private func resetPlayer() {
let cleanup = {
self.mediaListPlayer.stop()
self.mediaListPlayer.mediaPlayer.stop()
self.mediaListPlayer.mediaPlayer.delegate = nil
self.mediaListPlayer.mediaPlayer.drawable = nil
self.mediaListPlayer.mediaPlayer.media = nil
self.mediaListPlayer.rootMedia = nil
self.currentMediaURL = nil
}

if Thread.isMainThread {
cleanup()
} else {
DispatchQueue.main.async(execute: cleanup)
}
}

Expand Down Expand Up @@ -113,10 +136,7 @@ class VLCMediaListPlayerUIView: UIView, VLCMediaPlayerDelegate {
/// Clean up the media player to prevent callbacks on deallocated objects.
public static func dismantleUIView(_ uiView: VLCMediaListPlayerUIView, coordinator: VLCVideoView.Coordinator) {
DispatchQueue.main.async {
uiView.mediaListPlayer.mediaPlayer.delegate = nil
uiView.mediaListPlayer.mediaPlayer.drawable = nil
uiView.mediaListPlayer.stop()
uiView.mediaListPlayer.rootMedia = nil
uiView.resetPlayer()
}
}

Expand All @@ -126,11 +146,7 @@ class VLCMediaListPlayerUIView: UIView, VLCMediaPlayerDelegate {

deinit {
debugPrint("🧹 VLCMediaListPlayerUIView deinit")
mediaListPlayer.stop()
mediaListPlayer.mediaPlayer.delegate = nil
mediaListPlayer.mediaPlayer.drawable = nil
mediaListPlayer.rootMedia = nil
mediaListPlayer.mediaPlayer.media = nil
resetPlayer()
}

func setDelegate(_ delegate: VLCMediaPlayerDelegate) {
Expand Down
10 changes: 1 addition & 9 deletions swiftchan/Views/Media/VLC/VLCVideoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,7 @@ struct VLCVideoView: UIViewRepresentable {
}

public static func dismantleUIView(_ uiView: VLCMediaListPlayerUIView, coordinator: VLCVideoView.Coordinator) {
DispatchQueue.main.async {
// Detach delegate and clear drawable to avoid callbacks on deallocated objects
uiView.mediaListPlayer.mediaPlayer.delegate = nil
uiView.mediaListPlayer.mediaPlayer.drawable = nil
// Stop and clear the media to ensure proper cleanup
uiView.mediaListPlayer.stop()
uiView.mediaListPlayer.mediaPlayer.media = nil
uiView.mediaListPlayer.rootMedia = nil
}
VLCMediaListPlayerUIView.dismantleUIView(uiView, coordinator: coordinator)
}

// MARK: Coordinator
Expand Down
11 changes: 11 additions & 0 deletions swiftchanTests/WebmValidationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XCTest
@testable import swiftchan

final class WebmValidationTests: XCTestCase {
func testValidWebmHeader() throws {
let url = FileManager.default.temporaryDirectory.appendingPathComponent("sample.webm")
let bytes: [UInt8] = [0x1A, 0x45, 0xDF, 0xA3]
try Data(bytes).write(to: url)
XCTAssertTrue(CacheManager.shared.isValidWebm(file: url))
}
}
Loading