Files
Woka_Native_iOS/WOKA/Theme/Controller/PlayerVC.swift
2024-08-26 19:43:41 +05:30

453 lines
17 KiB
Swift

//
// PlayerVC.swift
// WOKA
//
// Created by MacBook Pro on 30/05/24.
import UIKit
import JWPlayerKit
import AVKit
class PlayerVC: JWPlayerViewController, JWPlayerViewControllerDelegate {
@IBOutlet weak var backButton: UIButton!
var previousScale: CGFloat = 1.0
var contentType : VideoContentType?
var config: JWPlayerConfiguration!
var dismissTapped: (() -> Void)?
var videoIndex : Int?
var errorCount = 0
var isFullScreenBtn = false
var vm = PlayerVM()
deinit {
NotificationCenter.default.removeObserver(self,name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.removeObserver(self,name: UIApplication.willEnterForegroundNotification, object: nil)
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .allButUpsideDown
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
if #available(iOS 16.0, *) {
// Code for iOS 16.0 and above
return false
} else {
// Fallback code for earlier iOS versions
return true
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
player.configurePlayer(with: config)
vm.vc = self
vm.initView()
//bring back button to the front
self.view.bringSubviewToFront(backButton)
NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
// for ios 15 and below
func rotateView(to angle: CGFloat) {
// Apply rotation to the view's transform
view.transform = CGAffineTransform(rotationAngle: angle)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 16.0, *) {
// Code for iOS 15.0 and above
print("Running on iOS 15.0 or later")
appDelegate.deviceOrientation = .landscapeRight
let value = UIInterfaceOrientation.landscapeRight.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
} else {
// Fallback code for earlier iOS versions
rotateView(to: .pi / 2) // Example: 90 degrees rotation
print("Running on a version earlier than iOS 15.0")
}
//Disable Picture in Picture
playerView.allowsPictureInPicturePlayback = false
playerView.captionStyle = .none
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
player.stop()
}
@IBAction func backBtnTapped(_ sender: UIButton) {
switch contentType {
case .liveStream:
if let postID = vm.videoIDs.first {
let duration = DateFormatterLib.dateDifferenceINT(date1: vm.startTimeStamp, date2: Date())
let totalDuration = duration + vm.totalVideoViewTime
AuthFunc.shareInstance.userVideoView(postID: postID, postType: PostType.liveTV.rawValue, duration: totalDuration, catID: 0) { _ in}
vm.handleBackAction()
}
case .webSeries:
if let catID = vm.catID, vm.currentPlayingIndex >= 0 && vm.currentPlayingIndex < (vm.videoIDs.count - 1) {
let postID = vm.videoIDs[vm.currentPlayingIndex]
let duration = DateFormatterLib.dateDifferenceINT(date1: vm.startTimeStamp, date2: Date())
let totalDuration = duration + vm.totalVideoViewTime
Utilities.startProgressHUD(msg: K.ConstantString.sync)
AuthFunc.shareInstance.userVideoView(postID: postID, postType: PostType.episode.rawValue, duration: totalDuration, catID: catID) { [weak self] isDone in
guard let self else{return}
if isDone{
Utilities.dismissProgressHUD()
vm.handleBackAction()
K.GVar.reloadContinueWebSeries = true
}else{
Utilities.dismissProgressHUD()
vm.handleBackAction()
}
}
} else {
vm.handleBackAction()
}
case .trailer:
let duration = DateFormatterLib.dateDifferenceINT(date1: vm.startTimeStamp, date2: Date())
let totalDuration = duration + vm.totalVideoViewTime
AuthFunc.shareInstance.userVideoView(postID: 0, postType: PostType.episode.rawValue, duration: totalDuration, catID: 0) { _ in}
vm.handleBackAction()
case .continueWatching:
if let catID = vm.catID,let postID = vm.videoIDs.first{
let duration = DateFormatterLib.dateDifferenceINT(date1: vm.startTimeStamp, date2: Date())
let totalDuration = duration + vm.totalVideoViewTime
Utilities.startProgressHUD(msg: K.ConstantString.sync)
AuthFunc.shareInstance.userVideoView(postID: postID, postType: PostType.episode.rawValue, duration: totalDuration, catID: catID) { [weak self] isDone in
guard let self else{return}
if isDone{
Utilities.dismissProgressHUD()
vm.handleBackAction()
K.GVar.reloadContinueWebSeries = true
}else{
Utilities.dismissProgressHUD()
vm.handleBackAction()
}
}
} else {
vm.handleBackAction()
}
case .audioBooks:
if let postID = vm.videoIDs.first {
let duration = DateFormatterLib.dateDifferenceINT(date1: vm.startTimeStamp, date2: Date())
let totalDuration = duration + vm.totalVideoViewTime
Utilities.startProgressHUD(msg: K.ConstantString.sync)
AuthFunc.shareInstance.userVideoView(postID: postID, postType: PostType.audio.rawValue, duration: totalDuration, catID: 0) { [weak self] isDone in
guard let self else{
Utilities.dismissProgressHUD()
return
}
if isDone{
Utilities.dismissProgressHUD()
K.GVar.reloadContinueAudioBooks = true
vm.handleBackAction()
}else{
Utilities.dismissProgressHUD()
vm.handleBackAction()
}
}
}
case .games:
break
case .songs:
break
default:
vm.handleBackAction()
}
}
// MARK: - App LifeCycle Handling
@objc func appDidEnterBackground() {
print("App entered background PlayerVC")
player.pause()
//Update uservideo view
switch contentType {
case .liveStream:
vm.updateUserView()
case .webSeries:
vm.updateUserView()
case .trailer:
vm.updateUserView()
case .continueWatching:
vm.updateUserView()
case .audioBooks:
vm.updateUserView()
case .games:
break
case .songs:
break
default:
break
}
}
@objc func appWillEnterForeground() {
print("App will enter foreground PlayerVC")
//Reset StartTimestamp
vm.startTimeStamp = Date()
player.play()
}
// MARK: - Handle Screen Transition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil) { [weak self] _ in
guard let self else{return}
vm.checkOrientation()
}
}
// MARK: - JWPlayerViewControllerDelegate
override func jwplayerPlaylistHasCompleted(_ player: any JWPlayer) {
super.jwplayerPlaylistHasCompleted(player)
// if playlist is finised make sure to add it to user video view
vm.updateUserView()
print("PlayList Over", vm.currentPlayingIndex)
}
override func jwplayer(_ player: any JWPlayer, didLoadPlaylist playlist: [JWPlayerItem]) {
super.jwplayer(player, didLoadPlaylist: playlist)
print("Playlist loaded")
}
// this will give index
override func jwplayer(_ player: any JWPlayer, didLoadPlaylistItem item: JWPlayerItem, at index: UInt) {
super.jwplayer(player, didLoadPlaylistItem: item, at: index)
print("didLoadPlaylistItem ", index)
// if currentplaylist index is not set, and the video index that we got from back screen check with the load index
if contentType == .webSeries{
if let videoIndex,vm.currentPlayingIndex == -1 && (index != videoIndex){
return
}
if vm.currentPlayingIndex != -1 && vm.currentPlayingIndex != index{
// so update the loaded index and increase the current playing index
vm.updateUserView()
vm.currentPlayingIndex = Int(index)
}else{
vm.currentPlayingIndex = Int(index)
}
}
}
override func jwplayer(_ player: any JWPlayer, didFinishLoadingWithTime loadTime: TimeInterval) {
super.jwplayer(player, didFinishLoadingWithTime: loadTime)
print("LoadTime", loadTime)
// if let videoIndex , contentType == .webSeries, videoIndex != 0{
// player.nextUpPlaylistIndex = videoIndex
// player.next()
// self.videoIndex = nil
// }
}
override func jwplayerIsReady(_ player: JWPlayer) {
super.jwplayerIsReady(player)
switch contentType {
case .liveStream:
player.play()
case .webSeries:
break
// player.loadPlayerItemAt(index: videoIndex ?? 0)
// self.player.play(relatedContent: videoIndex ?? 0)
// self.player.loadPlayerItemAt(index: videoIndex ?? 0)
// player.play()
// if videoIndex == 0{
// player.seek(to: 0)
// }else{
// player.nextUpPlaylistIndex = videoIndex ?? 0
// player.next()
// }
case .trailer,.songs:
break
case .continueWatching,.audioBooks, .games:
player.seek(to: 0)
player.play()
case nil:
break
}
print("IsReady")
}
override func jwplayer(_ player: JWPlayer, failedWithSetupError code: UInt, message: String) {
super.jwplayer(player, failedWithSetupError: code, message: message)
print("Setup Error: \(code) - \(message)")
}
override func jwplayer(_ player: JWPlayer, failedWithError code: UInt, message: String) {
super.jwplayer(player, failedWithError: code, message: message)
print("Error: \(code) - \(message)")
// try reconnect 3 times else show retry
if errorCount < 4{
errorCount += 1
self.player.configurePlayer(with: config)
self.player.play()
return
}
DispatchQueue.main.async {
Utilities.alertWithBtnCancelCompletion(title: "Error", msgBody: message, okBtnStr: "Connect", vc: self) { [weak self] isDone in
guard let self else{
self?.vm.handleBackAction()
return
}
errorCount = 0
if isDone{
self.player.configurePlayer(with: config)
self.player.play()
}else{
self.vm.handleBackAction()
}
}
}
}
override func jwplayer(_ player: JWPlayer, encounteredWarning code: UInt, message: String) {
super.jwplayer(player, encounteredWarning: code, message: message)
//Handle the reconnecting of video here
print("Warning: \(code) - \(message)")
}
override func jwplayer(_ player: JWPlayer, encounteredAdError code: UInt, message: String) {
super.jwplayer(player, encounteredAdError: code, message: message)
print("Ad Error: \(code) - \(message)")
}
override func jwplayer(_ player: JWPlayer, encounteredAdWarning code: UInt, message: String) {
super.jwplayer(player, encounteredAdWarning: code, message: message)
print("Ad Warning: \(code) - \(message)")
}
override func jwplayer(_ player: JWPlayer, isBufferingWithReason reason: JWBufferReason) {
super.jwplayer(player, isBufferingWithReason: reason)
print("Buffering Reason:", reason)
}
//When Player is Paused
override func jwplayer(_ player: JWPlayer, didPauseWithReason reason: JWPauseReason) {
super.jwplayer(player, didPauseWithReason: reason)
if reason == .interaction{
vm.updateClicks()
}
// Implement custom behavior
}
//When Player is Play
override func jwplayer(_ player: JWPlayer, isPlayingWithReason reason: JWPlayReason) {
super.jwplayer(player, isPlayingWithReason: reason)
if reason == .interaction{
vm.updateClicks()
}
}
}
// MARK: - Full Screen Handling
extension PlayerVC {
func playerViewControllerWillGoFullScreen(_ controller: JWPlayerViewController) -> JWFullScreenViewController? {
print("playerViewControllerWillGoFullScreen")
self.interfaceBehavior = .hidden
self.player.stop()
controller.player.stop()
if contentType == .liveStream{
self.dismissTapped?()
}
return nil
}
func playerViewControllerDidGoFullScreen(_ controller: JWPlayerViewController) {
print("playerViewControllerDidGoFullScreen")
self.player.pause()
self.dismissFullScreen(animated: false)
self.dismiss(animated: true)
return
}
func playerViewControllerWillDismissFullScreen(_ controller: JWPlayerViewController) {
switch contentType {
case .audioBooks,.liveStream,.webSeries, .continueWatching, .trailer:
vm.updateUserView()
default:
break
}
print("playerViewControllerWillDismissFullScreen")
}
func playerViewControllerDidDismissFullScreen(_ controller: JWPlayerViewController) {
print("playerViewControllerDidDismissFullScreen")
vm.updateClicks()
if #available(iOS 16.0, *) {
// Code for iOS 16.0 and above
appDelegate.deviceOrientation = .portrait
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
} else {
// Fallback code for earlier iOS versions
self.dismiss(animated: true)
}
}
}
// MARK: - JWPlayerViewController Delegate Functions
extension PlayerVC {
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, controlBarVisibilityChanged isVisible: Bool, frame: CGRect) {
self.backButton.isHidden = !isVisible
}
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, sizeChangedFrom oldSize: CGSize, to newSize: CGSize) {
// Handle size change if necessary
}
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, screenTappedAt position: CGPoint) {
// Handle screen tap if necessary
}
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerKit.JWPlayerItem], withMethod method: JWPlayerKit.JWRelatedInteraction) {
print("Related items:", items)
}
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuClosedWithMethod method: JWRelatedInteraction) {
print("Related menu closed")
}
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerKit.JWPlayerItem, atIndex index: Int, withMethod method: JWPlayerKit.JWRelatedMethod) {
print("Item ", item, index)
}
}