- Av player enhancements
This commit is contained in:
2
Podfile
2
Podfile
@@ -14,7 +14,7 @@ target 'WOKA' do
|
||||
pod 'Alamofire' , '~> 5.9.1'
|
||||
|
||||
# Image Loading & Caching
|
||||
pod 'SDWebImage', '~> 5.19.1'
|
||||
pod 'SDWebImage' , '~> 5.19.4'
|
||||
|
||||
#JwPlayer
|
||||
# pod 'JWPlayerKit', '>= 4.0.0'
|
||||
|
||||
26
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/Contents.json
vendored
Normal file
26
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "refresh-arrow (1).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "refresh-arrow (1)@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "refresh-arrow (1)@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/refresh-arrow (1).png
vendored
Normal file
BIN
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/refresh-arrow (1).png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/refresh-arrow (1)@2x.png
vendored
Normal file
BIN
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/refresh-arrow (1)@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
BIN
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/refresh-arrow (1)@3x.png
vendored
Normal file
BIN
WOKA/Assets/Assets.xcassets/Karaoke/Reload.imageset/refresh-arrow (1)@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -9,16 +9,20 @@ import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class AVPlayerVC: UIViewController {
|
||||
|
||||
@IBOutlet weak var videoPlayer: UIView!
|
||||
// @IBOutlet weak var videoPlayerHeight: NSLayoutConstraint!
|
||||
|
||||
@IBOutlet weak var viewControll: UIView!
|
||||
@IBOutlet weak var stackCtrView: UIStackView!
|
||||
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
|
||||
|
||||
@IBOutlet weak var sliderStack: UIStackView!
|
||||
@IBOutlet weak var tintView: UIView!
|
||||
@IBOutlet weak var videoTitle: UILabel!
|
||||
|
||||
@IBOutlet weak var karaokeStack: UIStackView!
|
||||
@IBOutlet weak var playPauseRecordStack: UIStackView!
|
||||
@IBOutlet weak var karaokeLoading: UIActivityIndicatorView!
|
||||
@IBOutlet weak var reloadBtn: UIButton!
|
||||
|
||||
@IBOutlet weak var img10SecBack: UIImageView! {
|
||||
didSet {
|
||||
self.img10SecBack.isUserInteractionEnabled = true
|
||||
@@ -48,276 +52,214 @@ class AVPlayerVC: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var videoURL : String?
|
||||
var timer : Timer?
|
||||
var titleVideo : String?
|
||||
var vm = AVPlayerVM()
|
||||
|
||||
|
||||
// MARK: - LifeCycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
vm.vc = self
|
||||
vm.initView()
|
||||
self.videoTitle.text = titleVideo
|
||||
startTimer()
|
||||
self.setObserverToPlayer()
|
||||
|
||||
hideShowIndicator(isLoading: true)
|
||||
|
||||
viewControll.addTapGesture {
|
||||
self.timer?.invalidate()
|
||||
self.showHideControls()
|
||||
self.vm.timer?.invalidate()
|
||||
self.vm.showHideControls()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
self.setVideoPlayer()
|
||||
self.vm.setVideoPlayer()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.vm.player?.pause()
|
||||
self.vm.player = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
self.vm.player?.currentItem?.removeObserver(self, forKeyPath: "status")
|
||||
|
||||
vm.player?.removeObserver(self, forKeyPath: "rate")
|
||||
if let playerItem = vm.player?.currentItem {
|
||||
playerItem.removeObserver(self, forKeyPath: "isPlaybackBufferEmpty")
|
||||
playerItem.removeObserver(self, forKeyPath: "isPlaybackLikelyToKeepUp")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Button Clicks & Tap Handler
|
||||
|
||||
@IBAction func reloadBtnTapped(_ sender: UIButton) {
|
||||
vm.isFinished = false
|
||||
|
||||
vm.reloadVideo()
|
||||
}
|
||||
|
||||
@IBAction func closeBtnTapped(_ sender: UIButton) {
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func startTimer(){
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 3.5, repeats: false) { _ in
|
||||
self.showHideControls()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.player?.pause()
|
||||
self.player = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
player?.currentItem?.removeObserver(self, forKeyPath: "status")
|
||||
}
|
||||
|
||||
// MARK: - ShowHideControls
|
||||
|
||||
func showHideControls(){
|
||||
stackCtrView.isHidden.toggle()
|
||||
sliderStack.isHidden.toggle()
|
||||
tintView.isHidden.toggle()
|
||||
|
||||
if !stackCtrView.isHidden{
|
||||
startTimer()
|
||||
}
|
||||
@IBAction func startStopRecording(_ sender: LocalisedElementsButton) {
|
||||
}
|
||||
|
||||
|
||||
private var player : AVPlayer? = nil
|
||||
private var playerLayer : AVPlayerLayer? = nil
|
||||
|
||||
private func setVideoPlayer() {
|
||||
guard let videoURL, let url = URL(string: videoURL) else { return }
|
||||
|
||||
if self.player == nil {
|
||||
// Initialize the player
|
||||
let playerItem = AVPlayerItem(url: url)
|
||||
player = AVPlayer(playerItem: playerItem)
|
||||
|
||||
// Add observer for AVPlayerItemFailedToPlayToEndTimeNotification
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(playerItemFailedToPlayToEndTime(_:)),
|
||||
name: .AVPlayerItemFailedToPlayToEndTime,
|
||||
object: playerItem)
|
||||
|
||||
// Add observer for AVPlayerItem's status
|
||||
playerItem.addObserver(self,
|
||||
forKeyPath: "status",
|
||||
options: [.new, .initial],
|
||||
context: nil)
|
||||
|
||||
// Set up the player layer
|
||||
playerLayer = AVPlayerLayer(player: player)
|
||||
playerLayer?.videoGravity = .resizeAspectFill
|
||||
playerLayer?.frame = videoPlayer.bounds
|
||||
|
||||
if let playerLayer = playerLayer {
|
||||
videoPlayer.layer.addSublayer(playerLayer)
|
||||
}
|
||||
|
||||
|
||||
// self.player = AVPlayer(url: url)
|
||||
// self.playerLayer = AVPlayerLayer(player: self.player)
|
||||
// self.playerLayer?.videoGravity = .resizeAspectFill
|
||||
// self.playerLayer?.frame = self.videoPlayer.bounds
|
||||
// self.playerLayer?.addSublayer(self.viewControll.layer)
|
||||
// if let playerLayer = self.playerLayer {
|
||||
// self.videoPlayer.layer.addSublayer(playerLayer)
|
||||
// }
|
||||
// self.player?.play()
|
||||
// self.imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
}
|
||||
@IBAction func playPauseBtn(_ sender: LocalisedElementsButton) {
|
||||
}
|
||||
|
||||
private var timeObserver : Any? = nil
|
||||
|
||||
private func setObserverToPlayer() {
|
||||
let interval = CMTime(seconds: 0.3, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
timeObserver = player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { elapsed in
|
||||
self.updatePlayerTime()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Handle notification
|
||||
@objc func playerItemFailedToPlayToEndTime(_ notification: Notification) {
|
||||
if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
handlePlayerError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Observe for player item status changes
|
||||
override func observeValue(forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey : Any]?,
|
||||
context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "status" {
|
||||
if let playerItem = object as? AVPlayerItem {
|
||||
switch playerItem.status {
|
||||
case .readyToPlay:
|
||||
// Ready to play
|
||||
print("Player item is ready to play.")
|
||||
player?.play()
|
||||
imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
case .failed:
|
||||
// Failed
|
||||
if let error = playerItem.error {
|
||||
print("Player item error: \(error.localizedDescription)")
|
||||
handlePlayerError(error)
|
||||
}
|
||||
case .unknown:
|
||||
// Unknown status
|
||||
print("Player item status is unknown.")
|
||||
@unknown default:
|
||||
// Handle unexpected cases
|
||||
print("Player item status is unknown.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handlePlayerError(_ error: Error) {
|
||||
// Update the UI or show an alert to the user
|
||||
Utilities.alertWithBtnCompletion(title: "Error", msgBody: error.localizedDescription, okBtnStr: "Retry?", vc: self) { [weak self] isDone in
|
||||
guard let self else{return}
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func updatePlayerTime() {
|
||||
guard let currentTime = self.player?.currentTime() else { return }
|
||||
guard let duration = self.player?.currentItem?.duration else { return }
|
||||
|
||||
let currentTimeInSecond = CMTimeGetSeconds(currentTime)
|
||||
let durationTimeInSecond = CMTimeGetSeconds(duration)
|
||||
|
||||
if self.isThumbSeek == false {
|
||||
self.seekSlider.value = Float(currentTimeInSecond/durationTimeInSecond)
|
||||
}
|
||||
|
||||
let value = Float64(self.seekSlider.value) * CMTimeGetSeconds(duration)
|
||||
|
||||
var hours = value / 3600
|
||||
var mins = (value / 60).truncatingRemainder(dividingBy: 60)
|
||||
var secs = value.truncatingRemainder(dividingBy: 60)
|
||||
var timeformatter = NumberFormatter()
|
||||
timeformatter.minimumIntegerDigits = 2
|
||||
timeformatter.minimumFractionDigits = 0
|
||||
timeformatter.roundingMode = .down
|
||||
guard let hoursStr = timeformatter.string(from: NSNumber(value: hours)), let minsStr = timeformatter.string(from: NSNumber(value: mins)), let secsStr = timeformatter.string(from: NSNumber(value: secs)) else {
|
||||
return
|
||||
}
|
||||
self.lbCurrentTime.text = "\(hoursStr):\(minsStr):\(secsStr)"
|
||||
|
||||
hours = durationTimeInSecond / 3600
|
||||
mins = (durationTimeInSecond / 60).truncatingRemainder(dividingBy: 60)
|
||||
secs = durationTimeInSecond.truncatingRemainder(dividingBy: 60)
|
||||
timeformatter = NumberFormatter()
|
||||
timeformatter.minimumIntegerDigits = 2
|
||||
timeformatter.minimumFractionDigits = 0
|
||||
timeformatter.roundingMode = .down
|
||||
guard let hoursStr = timeformatter.string(from: NSNumber(value: hours)), let minsStr = timeformatter.string(from: NSNumber(value: mins)), let secsStr = timeformatter.string(from: NSNumber(value: secs)) else {
|
||||
return
|
||||
}
|
||||
self.lbTotalTime.text = "\(hoursStr):\(minsStr):\(secsStr)"
|
||||
}
|
||||
|
||||
|
||||
@objc private func onTap10SecNext() {
|
||||
guard let currentTime = self.player?.currentTime() else { return }
|
||||
@objc func onTap10SecNext() {
|
||||
guard let currentTime = self.vm.player?.currentTime() else { return }
|
||||
let seekTime10Sec = CMTimeGetSeconds(currentTime).advanced(by: 10)
|
||||
let seekTime = CMTime(value: CMTimeValue(seekTime10Sec), timescale: 1)
|
||||
self.player?.seek(to: seekTime, completionHandler: { completed in
|
||||
|
||||
vm.timer?.invalidate()
|
||||
vm.timer = nil
|
||||
hideShowIndicator(isLoading: true)
|
||||
|
||||
self.vm.player?.seek(to: seekTime, completionHandler: { [weak self] completed in
|
||||
if completed{
|
||||
guard let self else{return}
|
||||
hideShowIndicator(isLoading: false)
|
||||
vm.startTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@objc private func onTap10SecBack() {
|
||||
guard let currentTime = self.player?.currentTime() else { return }
|
||||
@objc func onTap10SecBack() {
|
||||
guard let currentTime = self.vm.player?.currentTime() else { return }
|
||||
vm.timer?.invalidate()
|
||||
vm.timer = nil
|
||||
hideShowIndicator(isLoading: true)
|
||||
|
||||
let seekTime10Sec = CMTimeGetSeconds(currentTime).advanced(by: -10)
|
||||
let seekTime = CMTime(value: CMTimeValue(seekTime10Sec), timescale: 1)
|
||||
self.player?.seek(to: seekTime, completionHandler: { completed in
|
||||
|
||||
self.vm.player?.seek(to: seekTime, completionHandler: { [weak self] completed in
|
||||
if completed{
|
||||
guard let self else{return}
|
||||
hideShowIndicator(isLoading: false)
|
||||
vm.startTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@objc private func onTapPlayPause() {
|
||||
if self.player?.timeControlStatus == .playing {
|
||||
self.imgPlay.image = UIImage(systemName: "play.circle")
|
||||
self.player?.pause()
|
||||
} else {
|
||||
self.imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
self.player?.play()
|
||||
func hideShowIndicator(isLoading : Bool){
|
||||
if isLoading{
|
||||
self.imgPlay.isHidden = true
|
||||
self.loadingIndicator.startAnimating()
|
||||
}else{
|
||||
self.imgPlay.isHidden = false
|
||||
self.loadingIndicator.stopAnimating()
|
||||
self.loadingIndicator.hidesWhenStopped = true
|
||||
}
|
||||
}
|
||||
|
||||
private var isThumbSeek : Bool = false
|
||||
@objc private func onTapToSlide() {
|
||||
if timer != nil{
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
@objc func onTapPlayPause() {
|
||||
if self.vm.player?.timeControlStatus == .playing {
|
||||
self.imgPlay.image = UIImage(systemName: "play.circle")
|
||||
self.vm.player?.pause()
|
||||
} else {
|
||||
self.imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
self.vm.player?.play()
|
||||
}
|
||||
self.isThumbSeek = true
|
||||
guard let duration = self.player?.currentItem?.duration else { return }
|
||||
}
|
||||
|
||||
@objc func onTapToSlide() {
|
||||
if vm.timer != nil{
|
||||
vm.timer?.invalidate()
|
||||
vm.timer = nil
|
||||
}
|
||||
self.vm.isThumbSeek = true
|
||||
guard let duration = self.vm.player?.currentItem?.duration else { return }
|
||||
let value = Float64(self.seekSlider.value) * CMTimeGetSeconds(duration)
|
||||
if value.isNaN == false {
|
||||
let seekTime = CMTime(value: CMTimeValue(value), timescale: 1)
|
||||
self.player?.seek(to: seekTime, completionHandler: { completed in
|
||||
self.vm.player?.seek(to: seekTime, completionHandler: { completed in
|
||||
if completed {
|
||||
self.isThumbSeek = false
|
||||
self.startTimer()
|
||||
// print("Completed")
|
||||
self.vm.isThumbSeek = false
|
||||
self.vm.startTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func onTapToggleScreen() {
|
||||
if #available(iOS 16.0, *) {
|
||||
guard let windowSceen = self.view.window?.windowScene else { return }
|
||||
if windowSceen.interfaceOrientation == .portrait {
|
||||
windowSceen.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
} else {
|
||||
windowSceen.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in
|
||||
print(error.localizedDescription)
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "rate" {
|
||||
if let player = object as? AVPlayer {
|
||||
if player.rate == 0 {
|
||||
print("Player is paused")
|
||||
} else {
|
||||
print("Player is playing")
|
||||
hideShowIndicator(isLoading: false)
|
||||
imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if UIDevice.current.orientation == .portrait {
|
||||
let orientation = UIInterfaceOrientation.landscapeRight.rawValue
|
||||
UIDevice.current.setValue(orientation, forKey: "orientation")
|
||||
} else {
|
||||
let orientation = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(orientation, forKey: "orientation")
|
||||
} else if let playerItem = object as? AVPlayerItem {
|
||||
if keyPath == "isPlaybackBufferEmpty" {
|
||||
if playerItem.isPlaybackBufferEmpty {
|
||||
print("Loading")
|
||||
hideShowIndicator(isLoading: true)
|
||||
}
|
||||
} else if keyPath == "isPlaybackLikelyToKeepUp" {
|
||||
if playerItem.isPlaybackLikelyToKeepUp {
|
||||
print("finished")
|
||||
hideShowIndicator(isLoading: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Observe for player item status changes
|
||||
// override func observeValue(forKeyPath keyPath: String?,
|
||||
// of object: Any?,
|
||||
// change: [NSKeyValueChangeKey : Any]?,
|
||||
// context: UnsafeMutableRawPointer?) {
|
||||
// if keyPath == "status" {
|
||||
// if let playerItem = object as? AVPlayerItem {
|
||||
// switch playerItem.status {
|
||||
// case .readyToPlay:
|
||||
// // Ready to play
|
||||
// print("Player item is ready to play.")
|
||||
// vm.player?.play()
|
||||
// hideShowIndicator(isLoading: false)
|
||||
// imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
// case .failed:
|
||||
// // Failed
|
||||
// if let error = playerItem.error {
|
||||
// print("Player item error: \(error.localizedDescription)")
|
||||
// vm.handlePlayerError(error)
|
||||
// }
|
||||
// case .unknown:
|
||||
// // Unknown status
|
||||
// print("Player item status is unknown.")
|
||||
// @unknown default:
|
||||
// // Handle unexpected cases
|
||||
// print("Player item status is unknown.")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// @objc private func onTapToggleScreen() {
|
||||
// if #available(iOS 16.0, *) {
|
||||
// guard let windowSceen = self.view.window?.windowScene else { return }
|
||||
// if windowSceen.interfaceOrientation == .portrait {
|
||||
// windowSceen.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in
|
||||
// print(error.localizedDescription)
|
||||
// }
|
||||
// } else {
|
||||
// windowSceen.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in
|
||||
// print(error.localizedDescription)
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if UIDevice.current.orientation == .portrait {
|
||||
// let orientation = UIInterfaceOrientation.landscapeRight.rawValue
|
||||
// UIDevice.current.setValue(orientation, forKey: "orientation")
|
||||
// } else {
|
||||
// let orientation = UIInterfaceOrientation.portrait.rawValue
|
||||
// UIDevice.current.setValue(orientation, forKey: "orientation")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -178,55 +178,21 @@ class KaraokeDetailsVC: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
let avURL = URL(string: itemBuild.url)!
|
||||
let asset = AVAsset(url: avURL)
|
||||
|
||||
let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(karaokeData?.title?.trimmingCharacters(in: .whitespaces) ?? "extractedAudio").m4a")
|
||||
FileManager.default.clearTmpDirectory()
|
||||
asset.writeAudioTrackToURL(outputURL) { isDone, error,url in
|
||||
print(isDone, error , url)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else{return}
|
||||
Utilities.startProgressHUD()
|
||||
let sb = UIStoryboard(name: K.StoryBoard.Karaoke, bundle: nil)
|
||||
let vc = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Karaoke.aVPlayerVC) as! AVPlayerVC
|
||||
vc.videoURL = itemBuild.url
|
||||
vc.titleVideo = itemBuild.titles
|
||||
vc.modalPresentationStyle = .fullScreen
|
||||
self.present(vc, animated: true)
|
||||
// Dismiss the progress HUD after the view controller presentation
|
||||
Utilities.dismissProgressHUD()
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else{return}
|
||||
Utilities.startProgressHUD()
|
||||
let sb = UIStoryboard(name: K.StoryBoard.Karaoke, bundle: nil)
|
||||
let vc = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Karaoke.aVPlayerVC) as! AVPlayerVC
|
||||
vc.videoURL = itemBuild.url
|
||||
vc.modalPresentationStyle = .fullScreen
|
||||
self.present(vc, animated: true)
|
||||
// do {
|
||||
//
|
||||
// // Create a JWPlayerItem
|
||||
// let item = try JWPlayerItemBuilder()
|
||||
// .file(URL(string: itemBuild.url)!)
|
||||
// .posterImage(URL(string: itemBuild.poster ?? "")!)
|
||||
// .title(itemBuild.titles ?? "Karaoke")
|
||||
// .build()
|
||||
//
|
||||
// // Create a JWPlayerConfiguration
|
||||
// let config = try JWPlayerConfigurationBuilder()
|
||||
// .playlist(items: [item])
|
||||
// .autostart(true)
|
||||
// .build()
|
||||
//
|
||||
// vc.config = config
|
||||
// // vc.dismissTapped = self.tapped
|
||||
// vc.modalPresentationStyle = .overFullScreen
|
||||
// vc.modalTransitionStyle = .crossDissolve
|
||||
// vc.documentAudioUrl = url
|
||||
// Utilities.dismissProgressHUD()
|
||||
// // Present the PlayerVC
|
||||
// self.present(vc, animated: true)
|
||||
// } catch {
|
||||
// print("Error creating JWPlayer configuration: \(error)")
|
||||
// Utilities.dismissProgressHUD()
|
||||
// }
|
||||
|
||||
// Dismiss the progress HUD after the view controller presentation
|
||||
Utilities.dismissProgressHUD()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@IBAction func closeBtnTapped(_ sender: UIButton) {
|
||||
|
||||
@@ -602,10 +602,12 @@
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="VKq-Xn-4Nn">
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" toolTip="Slide" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="VKq-Xn-4Nn">
|
||||
<rect key="frame" x="54" y="0.0" width="296" height="31"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="thumbTintColor" red="0.80000000000000004" green="0.29411764709999999" blue="0.1137254902" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="minimumTrackTintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="maximumTrackTintColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="thumbTintColor" systemColor="systemRedColor"/>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="0xZ-En-mzk">
|
||||
<rect key="frame" x="354" y="0.0" width="50" height="30"/>
|
||||
@@ -625,13 +627,26 @@
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bfi-XW-XRq">
|
||||
<rect key="frame" x="187" y="105" width="40" height="40"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="ZgN-fk-hLp"/>
|
||||
<constraint firstAttribute="width" constant="40" id="sNR-VY-L1S"/>
|
||||
</constraints>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" image="Reload"/>
|
||||
<connections>
|
||||
<action selector="reloadBtnTapped:" destination="kQu-aO-Siv" eventType="touchUpInside" id="WGb-aJ-m6E"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="6xv-b4-3da">
|
||||
<rect key="frame" x="112" y="100" width="190" height="50"/>
|
||||
<rect key="frame" x="102" y="100" width="210" height="50"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M0h-b5-LXf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="goforward.10" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="IKL-qY-FkR">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="gobackward.10" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="IKL-qY-FkR">
|
||||
<rect key="frame" x="10" y="8.5" width="30" height="31"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
@@ -648,15 +663,24 @@
|
||||
<constraint firstAttribute="width" secondItem="M0h-b5-LXf" secondAttribute="height" multiplier="1:1" id="sg0-5u-jyP"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play.circle" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="IXg-pG-vEl">
|
||||
<rect key="frame" x="70" y="0.5" width="50" height="49"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="IXg-pG-vEl" secondAttribute="height" multiplier="1:1" id="HvT-nI-eDI"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TPR-zt-HTV">
|
||||
<rect key="frame" x="70" y="0.0" width="70" height="50"/>
|
||||
<subviews>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="sBj-K5-TGW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="20" height="50"/>
|
||||
<color key="color" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</activityIndicatorView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play.circle" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="IXg-pG-vEl">
|
||||
<rect key="frame" x="20" y="0.5" width="50" height="49"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="IXg-pG-vEl" secondAttribute="height" multiplier="1:1" id="HvT-nI-eDI"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Odf-qx-Pl1">
|
||||
<rect key="frame" x="140" y="0.0" width="50" height="50"/>
|
||||
<rect key="frame" x="160" y="0.0" width="50" height="50"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="goforward.10" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="5Xm-AY-vbf">
|
||||
<rect key="frame" x="10" y="8.5" width="30" height="31"/>
|
||||
@@ -703,7 +727,9 @@
|
||||
<constraint firstItem="l7y-7N-uz2" firstAttribute="leading" secondItem="s55-L0-QBA" secondAttribute="leading" id="Ppx-0A-xU4"/>
|
||||
<constraint firstAttribute="height" constant="250" id="VSu-g6-K76"/>
|
||||
<constraint firstItem="l7y-7N-uz2" firstAttribute="top" secondItem="s55-L0-QBA" secondAttribute="top" id="mcF-Rx-XSf"/>
|
||||
<constraint firstItem="bfi-XW-XRq" firstAttribute="centerX" secondItem="s55-L0-QBA" secondAttribute="centerX" id="o9m-rv-reu"/>
|
||||
<constraint firstAttribute="trailing" secondItem="l7y-7N-uz2" secondAttribute="trailing" id="r6B-rl-7Kz"/>
|
||||
<constraint firstItem="bfi-XW-XRq" firstAttribute="centerY" secondItem="s55-L0-QBA" secondAttribute="centerY" id="wKu-hs-VYd"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="hXe-Fv-Zk9">
|
||||
@@ -732,91 +758,92 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="jTd-8Q-0y7">
|
||||
<rect key="frame" x="25" y="658" width="364" height="96"/>
|
||||
<rect key="frame" x="25" y="658" width="364" height="86"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="gLz-mh-ln0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="364" height="50"/>
|
||||
<stackView hidden="YES" opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="vq6-xr-Jba">
|
||||
<rect key="frame" x="0.0" y="0.0" width="364" height="10"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4KA-5y-qg7" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="172" height="50"/>
|
||||
<color key="backgroundColor" red="0.80000000000000004" green="0.29411764709999999" blue="0.1137254902" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="3ZS-GJ-VKY"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="14"/>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="Yr0-AW-uh9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="364" height="0.0"/>
|
||||
<color key="color" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Loading Karaoke..." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jww-Ym-BQw">
|
||||
<rect key="frame" x="0.0" y="10" width="364" height="0.0"/>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="15"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="MVs-Fv-8PY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="364" height="86"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="gLz-mh-ln0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="364" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4KA-5y-qg7" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="172" height="50"/>
|
||||
<color key="backgroundColor" red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="3ZS-GJ-VKY"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="14"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
|
||||
<state key="normal" title="Start Recording" image="Microphone"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="dwL-Ob-ZbF"/>
|
||||
<action selector="startStopRecording:" destination="kQu-aO-Siv" eventType="touchUpInside" id="Ac4-9o-mfd"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dz2-DJ-auS" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="192" y="0.0" width="172" height="50"/>
|
||||
<color key="backgroundColor" name="TextDarkBlue"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="7d9-YY-TdQ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="16"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="-10" minY="0.0" maxX="10" maxY="0.0"/>
|
||||
<state key="normal" title="Play" image="PlayButtonSmall"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="hwK-OY-rQq"/>
|
||||
<action selector="playPauseBtn:" destination="kQu-aO-Siv" eventType="touchUpInside" id="cI8-j7-ILD"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="k45-7s-jnN" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="65" width="364" height="21"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="16"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
|
||||
<state key="normal" title="Start Recording" image="Microphone"/>
|
||||
<state key="normal" title="Download Recording" image="icloud.and.arrow.down.fill" catalog="system"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="dwL-Ob-ZbF"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dz2-DJ-auS" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="192" y="0.0" width="172" height="50"/>
|
||||
<color key="backgroundColor" name="TextDarkBlue"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="7d9-YY-TdQ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="16"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="-10" minY="0.0" maxX="10" maxY="0.0"/>
|
||||
<state key="normal" title="Play" image="PlayButtonSmall"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="hwK-OY-rQq"/>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="owb-s2-DxU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k45-7s-jnN" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="65" width="364" height="31"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
|
||||
<state key="normal">
|
||||
<attributedString key="attributedTitle">
|
||||
<fragment content="Download">
|
||||
<attributes>
|
||||
<font key="NSFont" metaFont="system" size="16"/>
|
||||
<font key="NSOriginalFont" size="16" name="Farah"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content=" ">
|
||||
<attributes>
|
||||
<font key="NSFont" size="16" name="Farah"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content="Recording">
|
||||
<attributes>
|
||||
<font key="NSFont" size="14" name="Exo2-Regular"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
</attributedString>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="owb-s2-DxU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
@@ -838,13 +865,17 @@
|
||||
<outlet property="img10SecBack" destination="IKL-qY-FkR" id="GuA-TX-UHu"/>
|
||||
<outlet property="img10SecFor" destination="5Xm-AY-vbf" id="LPy-C0-Twz"/>
|
||||
<outlet property="imgPlay" destination="IXg-pG-vEl" id="L33-Hc-mGN"/>
|
||||
<outlet property="karaokeLoading" destination="Yr0-AW-uh9" id="iRc-FO-gPb"/>
|
||||
<outlet property="karaokeStack" destination="vq6-xr-Jba" id="bZf-Re-O9f"/>
|
||||
<outlet property="lbCurrentTime" destination="PZq-WO-H32" id="n5q-bm-hVP"/>
|
||||
<outlet property="lbTotalTime" destination="0xZ-En-mzk" id="g1U-2U-svw"/>
|
||||
<outlet property="loadingIndicator" destination="sBj-K5-TGW" id="pAc-Hh-x7o"/>
|
||||
<outlet property="playPauseRecordStack" destination="MVs-Fv-8PY" id="l9s-Hj-0yj"/>
|
||||
<outlet property="reloadBtn" destination="bfi-XW-XRq" id="Rj3-kP-JxK"/>
|
||||
<outlet property="seekSlider" destination="VKq-Xn-4Nn" id="ims-DY-5HR"/>
|
||||
<outlet property="sliderStack" destination="Mhh-bR-zKm" id="Noa-Nd-AK4"/>
|
||||
<outlet property="stackCtrView" destination="6xv-b4-3da" id="BbF-dD-Iek"/>
|
||||
<outlet property="tintView" destination="7Lc-Yp-x8G" id="bOc-mG-HwG"/>
|
||||
<outlet property="videoPlayer" destination="s55-L0-QBA" id="OHf-Ru-O1Y"/>
|
||||
<outlet property="videoTitle" destination="alm-nS-A2I" id="RQN-Uc-WgF"/>
|
||||
<outlet property="viewControll" destination="l7y-7N-uz2" id="zly-5u-1kS"/>
|
||||
</connections>
|
||||
@@ -1045,10 +1076,13 @@
|
||||
<image name="LikeRemove" width="42.5" height="42.5"/>
|
||||
<image name="Microphone" width="31" height="31"/>
|
||||
<image name="PlayButtonSmall" width="28.333333969116211" height="28.333333969116211"/>
|
||||
<image name="Reload" width="42.5" height="42.5"/>
|
||||
<image name="ShareImage" width="18" height="18"/>
|
||||
<image name="WebSeriesSeasonsBackground" width="142.66667175292969" height="187.33332824707031"/>
|
||||
<image name="gobackward.10" catalog="system" width="119" height="128"/>
|
||||
<image name="goforward.10" catalog="system" width="119" height="128"/>
|
||||
<image name="hand.thumbsup.fill" catalog="system" width="128" height="121"/>
|
||||
<image name="icloud.and.arrow.down.fill" catalog="system" width="128" height="111"/>
|
||||
<image name="play.circle" catalog="system" width="128" height="123"/>
|
||||
<namedColor name="ImageDarkBlue">
|
||||
<color red="0.035000000149011612" green="0.0" blue="0.36500000953674316" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -1062,5 +1096,8 @@
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemRedColor">
|
||||
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -5,13 +5,243 @@
|
||||
// Created by Bilal on 05/07/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AVKit
|
||||
|
||||
class AVPlayerVM{
|
||||
|
||||
weak var vc : AVPlayerVC!
|
||||
|
||||
var player : AVPlayer? = nil
|
||||
var isThumbSeek : Bool = false
|
||||
var timer : Timer?
|
||||
var isFinished = false{
|
||||
didSet{
|
||||
if isFinished{
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
vc.reloadBtn.isHidden = false
|
||||
vc.stackCtrView.isHidden = true
|
||||
vc.tintView.isHidden = false
|
||||
vc.sliderStack.isHidden = true
|
||||
}else{
|
||||
vc.reloadBtn.isHidden = true
|
||||
vc.stackCtrView.isHidden = false
|
||||
vc.sliderStack.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var playerLayer : AVPlayerLayer? = nil
|
||||
private var timeObserver : Any? = nil
|
||||
|
||||
func initView(){
|
||||
vc.seekSlider.transform = CGAffineTransform(scaleX: 0.85, y: 0.85)
|
||||
|
||||
self.vc.videoTitle.text = vc.titleVideo
|
||||
setupKaraoke()
|
||||
|
||||
}
|
||||
|
||||
@objc func videoDidFinish() {
|
||||
self.isFinished = true
|
||||
|
||||
}
|
||||
|
||||
func setupKaraoke(){
|
||||
guard let url = vc.videoURL else{return}
|
||||
hideShowKaraoke(isLoading: true)
|
||||
let avURL = URL(string: url)!
|
||||
let asset = AVAsset(url: avURL)
|
||||
|
||||
let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(vc.titleVideo?.trimmingCharacters(in: .whitespaces) ?? "extractedAudio").m4a")
|
||||
FileManager.default.clearTmpDirectory()
|
||||
asset.writeAudioTrackToURL(outputURL) { [weak self] isDone, error,url in
|
||||
guard let self else{return}
|
||||
print(isDone, error , url)
|
||||
if error == nil{
|
||||
hideShowKaraoke(isLoading: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hideShowKaraoke(isLoading : Bool){
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else{return}
|
||||
if isLoading{
|
||||
vc.karaokeLoading.startAnimating()
|
||||
vc.karaokeStack.isHidden = false
|
||||
vc.playPauseRecordStack.isHidden = true
|
||||
}else{
|
||||
vc.karaokeLoading.stopAnimating()
|
||||
vc.karaokeLoading.hidesWhenStopped = true
|
||||
vc.karaokeStack.isHidden = true
|
||||
vc.playPauseRecordStack.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setup Video Player
|
||||
|
||||
func setVideoPlayer() {
|
||||
guard let videoURL = vc.videoURL, let url = URL(string: videoURL) else { return }
|
||||
|
||||
if self.player == nil {
|
||||
|
||||
// Create a subview for the video player
|
||||
let playerView = UIView(frame: vc.viewControll.bounds)
|
||||
playerView.backgroundColor = .clear // Make sure the background is clear
|
||||
vc.viewControll.addSubview(playerView)
|
||||
vc.viewControll.sendSubviewToBack(playerView) // Ensure the player view is below other UI elements
|
||||
|
||||
// Initialize the player
|
||||
let playerItem = AVPlayerItem(url: url)
|
||||
player = AVPlayer(playerItem: playerItem)
|
||||
|
||||
// Add observer for AVPlayerItemFailedToPlayToEndTimeNotification
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(playerItemFailedToPlayToEndTime(_:)),
|
||||
name: .AVPlayerItemFailedToPlayToEndTime,
|
||||
object: playerItem)
|
||||
|
||||
// Add observer for AVPlayerItem's status
|
||||
playerItem.addObserver(self.vc,
|
||||
forKeyPath: "status",
|
||||
options: [.new, .initial],
|
||||
context: nil)
|
||||
|
||||
// Set up the player layer
|
||||
playerLayer = AVPlayerLayer(player: player)
|
||||
playerLayer?.videoGravity = .resizeAspectFill
|
||||
playerLayer?.frame = playerView.bounds
|
||||
|
||||
if let playerLayer = playerLayer {
|
||||
playerView.layer.addSublayer(playerLayer)
|
||||
}
|
||||
|
||||
// Add observer for play/pause
|
||||
player?.addObserver(self.vc, forKeyPath: "rate", options: [.new, .initial], context: nil)
|
||||
|
||||
// Add observer for buffering
|
||||
playerItem.addObserver(self.vc, forKeyPath: "isPlaybackBufferEmpty", options: [.new, .initial], context: nil)
|
||||
playerItem.addObserver(self.vc, forKeyPath: "isPlaybackLikelyToKeepUp", options: [.new, .initial], context: nil)
|
||||
|
||||
// Add observer for video finished playing
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(videoDidFinish), name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
|
||||
self.setObserverToPlayer()
|
||||
startTimer()
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
func reloadVideo() {
|
||||
guard let videoURL = vc.videoURL, let url = URL(string: videoURL) else { return }
|
||||
|
||||
// Remove existing observers
|
||||
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
|
||||
player?.currentItem?.removeObserver(vc, forKeyPath: "status")
|
||||
player?.currentItem?.removeObserver(vc, forKeyPath: "isPlaybackBufferEmpty")
|
||||
player?.currentItem?.removeObserver(vc, forKeyPath: "isPlaybackLikelyToKeepUp")
|
||||
|
||||
// Create a new player item
|
||||
let playerItem = AVPlayerItem(url: url)
|
||||
player?.replaceCurrentItem(with: playerItem)
|
||||
|
||||
// Add observers again
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(videoDidFinish), name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
|
||||
playerItem.addObserver(vc, forKeyPath: "status", options: [.new, .initial], context: nil)
|
||||
playerItem.addObserver(vc, forKeyPath: "isPlaybackBufferEmpty", options: [.new, .initial], context: nil)
|
||||
playerItem.addObserver(vc, forKeyPath: "isPlaybackLikelyToKeepUp", options: [.new, .initial], context: nil)
|
||||
|
||||
player?.play()
|
||||
}
|
||||
|
||||
func setObserverToPlayer() {
|
||||
let interval = CMTime(seconds: 0.3, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
timeObserver = player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { elapsed in
|
||||
self.updatePlayerTime()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Handle notification
|
||||
@objc func playerItemFailedToPlayToEndTime(_ notification: Notification) {
|
||||
if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
handlePlayerError(error)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePlayerError(_ error: Error) {
|
||||
// Update the UI or show an alert to the user
|
||||
Utilities.alertWithBtnCompletion(title: "Error", msgBody: error.localizedDescription, okBtnStr: "Retry?", vc: vc.self) { [weak self] isDone in
|
||||
guard let self else{return}
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ShowHideControls
|
||||
func startTimer(){
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 4.5, repeats: false) { _ in
|
||||
self.showHideControls()
|
||||
}
|
||||
}
|
||||
|
||||
func showHideControls(){
|
||||
if isFinished{
|
||||
vc.stackCtrView.isHidden = true
|
||||
vc.reloadBtn.isHidden.toggle()
|
||||
vc.sliderStack.isHidden = true
|
||||
}else{
|
||||
vc.reloadBtn.isHidden = true
|
||||
vc.stackCtrView.isHidden.toggle()
|
||||
vc.sliderStack.isHidden.toggle()
|
||||
}
|
||||
|
||||
vc.tintView.isHidden.toggle()
|
||||
|
||||
if !vc.stackCtrView.isHidden{
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Update the player time Label with Slider
|
||||
|
||||
private func updatePlayerTime() {
|
||||
guard let currentTime = self.player?.currentTime() else { return }
|
||||
guard let duration = self.player?.currentItem?.duration else { return }
|
||||
|
||||
let currentTimeInSecond = CMTimeGetSeconds(currentTime)
|
||||
let durationTimeInSecond = CMTimeGetSeconds(duration)
|
||||
|
||||
if self.isThumbSeek == false {
|
||||
self.vc.seekSlider.value = Float(currentTimeInSecond/durationTimeInSecond)
|
||||
}
|
||||
|
||||
let value = Float64(self.vc.seekSlider.value) * CMTimeGetSeconds(duration)
|
||||
|
||||
var hours = value / 3600
|
||||
var mins = (value / 60).truncatingRemainder(dividingBy: 60)
|
||||
var secs = value.truncatingRemainder(dividingBy: 60)
|
||||
var timeformatter = NumberFormatter()
|
||||
timeformatter.minimumIntegerDigits = 2
|
||||
timeformatter.minimumFractionDigits = 0
|
||||
timeformatter.roundingMode = .down
|
||||
guard let hoursStr = timeformatter.string(from: NSNumber(value: hours)), let minsStr = timeformatter.string(from: NSNumber(value: mins)), let secsStr = timeformatter.string(from: NSNumber(value: secs)) else {
|
||||
return
|
||||
}
|
||||
self.vc.lbCurrentTime.text = "\(hoursStr):\(minsStr):\(secsStr)"
|
||||
|
||||
hours = durationTimeInSecond / 3600
|
||||
mins = (durationTimeInSecond / 60).truncatingRemainder(dividingBy: 60)
|
||||
secs = durationTimeInSecond.truncatingRemainder(dividingBy: 60)
|
||||
timeformatter = NumberFormatter()
|
||||
timeformatter.minimumIntegerDigits = 2
|
||||
timeformatter.minimumFractionDigits = 0
|
||||
timeformatter.roundingMode = .down
|
||||
guard let hoursStr = timeformatter.string(from: NSNumber(value: hours)), let minsStr = timeformatter.string(from: NSNumber(value: mins)), let secsStr = timeformatter.string(from: NSNumber(value: secs)) else {
|
||||
return
|
||||
}
|
||||
self.vc.lbTotalTime.text = "\(hoursStr):\(minsStr):\(secsStr)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +49,8 @@ class PlayerVC: JWPlayerViewController, JWPlayerViewControllerDelegate {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
player.configurePlayer(with: config)
|
||||
|
||||
self.rotateToLandsScapeDevice()
|
||||
self.delegate = self
|
||||
|
||||
//Disable Picture in Picture
|
||||
playerView.allowsPictureInPicturePlayback = false
|
||||
playerView.captionStyle = .none
|
||||
}
|
||||
|
||||
@objc func applicationDidBecomeActive() {
|
||||
@@ -75,6 +69,12 @@ class PlayerVC: JWPlayerViewController, JWPlayerViewControllerDelegate {
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
player.configurePlayer(with: config)
|
||||
self.delegate = self
|
||||
|
||||
//Disable Picture in Picture
|
||||
playerView.allowsPictureInPicturePlayback = false
|
||||
playerView.captionStyle = .none
|
||||
// self.navigationController?.isNavigationBarHidden = true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user