mirror of
https://github.com/cranci1/Sora.git
synced 2026-01-11 20:10:24 +00:00
let's text @bshar1865 code
Co-Authored-By: Bshar Esfky <98615778+bshar1865@users.noreply.github.com>
This commit is contained in:
parent
440ec57d59
commit
cc4c75f88a
2 changed files with 119 additions and 2 deletions
81
Sora/Utils/MediaPlayer/SubtitleManager.swift
Normal file
81
Sora/Utils/MediaPlayer/SubtitleManager.swift
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// SubtitleManager.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 10/06/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
class SubtitleManager {
|
||||
static let shared = SubtitleManager()
|
||||
private let subtitleLoader = VTTSubtitlesLoader()
|
||||
|
||||
private init() {}
|
||||
|
||||
func loadSubtitles(from url: URL) async throws -> [SubtitleCue] {
|
||||
return await withCheckedContinuation { continuation in
|
||||
subtitleLoader.load(from: url.absoluteString)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
continuation.resume(returning: self.subtitleLoader.cues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createSubtitleOverlay(for cues: [SubtitleCue], player: AVPlayer) -> SubtitleOverlayView {
|
||||
let overlay = SubtitleOverlayView()
|
||||
let interval = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
|
||||
player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
|
||||
let currentTime = time.seconds
|
||||
let currentCue = cues.first { cue in
|
||||
currentTime >= cue.startTime && currentTime <= cue.endTime
|
||||
}
|
||||
overlay.update(with: currentCue?.text ?? "")
|
||||
}
|
||||
|
||||
return overlay
|
||||
}
|
||||
}
|
||||
|
||||
class SubtitleOverlayView: UIView {
|
||||
private let label: UILabel = {
|
||||
let label = UILabel()
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
label.textColor = .white
|
||||
label.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
label.layer.shadowColor = UIColor.black.cgColor
|
||||
label.layer.shadowOffset = CGSize(width: 1, height: 1)
|
||||
label.layer.shadowOpacity = 0.8
|
||||
label.layer.shadowRadius = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupView()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupView()
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
backgroundColor = .clear
|
||||
addSubview(label)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
|
||||
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
|
||||
label.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
func update(with text: String) {
|
||||
label.text = text
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ class VideoPlayerViewController: UIViewController {
|
|||
var episodeNumber: Int = 0
|
||||
var episodeImageUrl: String = ""
|
||||
var mediaTitle: String = ""
|
||||
var subtitleOverlay: SubtitleOverlayView?
|
||||
|
||||
init(module: ScrapingModule) {
|
||||
self.module = module
|
||||
|
|
@ -66,6 +67,24 @@ class VideoPlayerViewController: UIViewController {
|
|||
playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
view.addSubview(playerViewController.view)
|
||||
playerViewController.didMove(toParent: self)
|
||||
|
||||
if !subtitles.isEmpty && UserDefaults.standard.bool(forKey: "subtitlesEnabled") {
|
||||
if let subtitleURL = URL(string: subtitles) {
|
||||
Task {
|
||||
do {
|
||||
let subtitleCues = try await SubtitleManager.shared.loadSubtitles(from: subtitleURL)
|
||||
await MainActor.run {
|
||||
if let player = self.player {
|
||||
let overlay = SubtitleManager.shared.createSubtitleOverlay(for: subtitleCues, player: player)
|
||||
self.addSubtitleOverlay(overlay)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("Failed to load subtitles: \(error.localizedDescription)", type: "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addPeriodicTimeObserver(fullURL: fullUrl)
|
||||
|
|
@ -113,8 +132,8 @@ class VideoPlayerViewController: UIViewController {
|
|||
guard let self = self,
|
||||
let currentItem = player.currentItem,
|
||||
currentItem.duration.seconds.isFinite else {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let currentTime = time.seconds
|
||||
let duration = currentItem.duration.seconds
|
||||
|
|
@ -158,6 +177,22 @@ class VideoPlayerViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private func addSubtitleOverlay(_ overlay: SubtitleOverlayView) {
|
||||
subtitleOverlay?.removeFromSuperview()
|
||||
subtitleOverlay = overlay
|
||||
|
||||
guard let playerView = playerViewController?.view else { return }
|
||||
playerView.addSubview(overlay)
|
||||
overlay.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
overlay.leadingAnchor.constraint(equalTo: playerView.leadingAnchor),
|
||||
overlay.trailingAnchor.constraint(equalTo: playerView.trailingAnchor),
|
||||
overlay.bottomAnchor.constraint(equalTo: playerView.bottomAnchor),
|
||||
overlay.heightAnchor.constraint(equalToConstant: 100)
|
||||
])
|
||||
}
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
if UserDefaults.standard.bool(forKey: "alwaysLandscape") {
|
||||
return .landscape
|
||||
|
|
@ -179,5 +214,6 @@ class VideoPlayerViewController: UIViewController {
|
|||
if let timeObserverToken = timeObserverToken {
|
||||
player?.removeTimeObserver(timeObserverToken)
|
||||
}
|
||||
subtitleOverlay?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue