Skip to content

Commit

Permalink
refactor: Add new Video player file to manage AVPlayer
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthieu-dgl committed Nov 5, 2024
1 parent a4fb80a commit 241750e
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Tuist/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/ios-core",
"state" : {
"revision" : "550e8fe4a4132570a63e144c88d095bfc7d5bac2",
"revision" : "688a8ff5f5dbc213bc4e752db97ed54e8570fc4c",
"version" : "12.3.2"
}
},
Expand Down
88 changes: 20 additions & 68 deletions kDrive/UI/View/Files/Preview/VideoCollectionViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ class VideoCollectionViewCell: PreviewCollectionViewCell {
private var playableFileName: String?
private var previewDownloadTask: Kingfisher.DownloadTask?
private var file: File!
private var timeObserverToken: Any?

private var player: AVPlayer? {
didSet {
playButton.isEnabled = player != nil
}
}
private var videoPlayer: VideoPlayer?

override func awakeFromNib() {
super.awakeFromNib()
Expand All @@ -60,8 +54,7 @@ class VideoCollectionViewCell: PreviewCollectionViewCell {

override func prepareForReuse() {
super.prepareForReuse()
player?.pause()
player = nil
videoPlayer?.stopPlayback()
previewFrameImageView.image = nil
previewDownloadTask?.cancel()
}
Expand All @@ -75,47 +68,22 @@ class VideoCollectionViewCell: PreviewCollectionViewCell {
self.previewFrameImageView.image = hasThumbnail ? preview : nil
}

setNowPlayingMetadata()

if !file.isLocalVersionOlderThanRemote {
player = AVPlayer(url: file.localUrl)
} else if let token = driveFileManager.apiFetcher.currentToken {
driveFileManager.apiFetcher.performAuthenticatedRequest(token: token) { token, _ in
if let token {
let url = Endpoint.download(file: file).url
let headers = ["Authorization": "Bearer \(token.accessToken)"]
let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
Task { @MainActor in
self.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
}
} else {
Task {
UIConstants.showSnackBar(message: KDriveResourcesStrings.Localizable.previewLoadError)
}
}
}
} else {
UIConstants.showSnackBar(message: KDriveResourcesStrings.Localizable.previewLoadError)
}
videoPlayer = VideoPlayer(file: file, driveFileManager: driveFileManager)
videoPlayer?.setNowPlayingMetadata(playableFileName: playableFileName)

if let player = player {
let interval = CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] _ in
self?.updateNowPlayingInfo()
}
videoPlayer?.onPlaybackEnded = { [weak self] in
self?.videoPlayer?.setNowPlayingMetadata(playableFileName: self?.playableFileName)
}
}

override func didEndDisplaying() {
MatomoUtils.trackMediaPlayer(leaveAt: player?.progressPercentage)
}

@objc private func playerDidPlayToEnd() {
setNowPlayingMetadata()
MatomoUtils.trackMediaPlayer(leaveAt: videoPlayer?.progressPercentage)
}

@IBAction func playVideoPressed(_ sender: Any) {
guard let player else { return }
guard let player = videoPlayer?.avPlayer else {
return
}

MatomoUtils.trackMediaPlayer(playMedia: .video)

Expand All @@ -129,44 +97,28 @@ class VideoCollectionViewCell: PreviewCollectionViewCell {
let navController = VideoPlayerNavigationController(rootViewController: playerViewController)
navController.disappearCallback = { [weak self] in
MatomoUtils.track(eventWithCategory: .mediaPlayer, name: "pause")
self?.player?.pause()
if let floatingPanelController = self?.floatingPanelController {
self?.parentViewController?.present(floatingPanelController, animated: true)
}
self?.videoPlayer?.stopPlayback()
self?.presentFloatingPanel()
}
navController.setNavigationBarHidden(true, animated: false)
navController.modalPresentationStyle = .overFullScreen
navController.modalTransitionStyle = .crossDissolve

floatingPanelController = parentViewController?.presentedViewController as? FloatingPanelController
floatingPanelController?.dismiss(animated: true)
presentFloatingPanel()
parentViewController?.present(navController, animated: true) {
playerViewController.player?.play()
}
}

private func setNowPlayingMetadata() {
var nowPlayingInfo = [String: Any]()

nowPlayingInfo[MPMediaItemPropertyTitle] = playableFileName ?? KDriveResourcesStrings.Localizable.unknownTitle
nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = false

if let player = player, let currentItem = player.currentItem {
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = CMTimeGetSeconds(currentItem.asset.duration)
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentTime())
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
private func presentFloatingPanel() {
if let floatingPanelController = parentViewController?.presentedViewController as? FloatingPanelController {
floatingPanelController.dismiss(animated: true)
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}

private func updateNowPlayingInfo() {
guard let player = player else { return }

var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo

nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentTime())
nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = player.rate

MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
private func presentVideoPlayer(navController: VideoPlayerNavigationController, playerViewController: AVPlayerViewController) {

Check warning on line 119 in kDrive/UI/View/Files/Preview/VideoCollectionViewCell.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Wrap lines that exceed the specified maximum width. (wrap)
parentViewController?.present(navController, animated: true) {
playerViewController.player?.play()
}
}
}
100 changes: 100 additions & 0 deletions kDriveCore/VideoPlayer/VideoPlayer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
Infomaniak kDrive - iOS App
Copyright (C) 2024 Infomaniak Network SA

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import AVKit
import FloatingPanel

Check warning on line 20 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import InfomaniakCore

Check warning on line 21 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import kDriveCore

Check warning on line 22 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import kDriveResources

Check warning on line 23 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import Kingfisher

Check warning on line 24 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import MediaPlayer

Check warning on line 25 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import UIKit

Check warning on line 26 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)
import Combine

Check warning on line 27 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Sort import statements alphabetically. (sortImports)

class VideoPlayer {
private var player: AVPlayer?
var onPlaybackEnded: (() -> Void)?

var avPlayer: AVPlayer? {
return player
}

var progressPercentage: Double {
guard let player = player else { return 0 }
return player.currentTime().seconds / (player.currentItem?.duration.seconds ?? 1)
}

init(file: File, driveFileManager: DriveFileManager) {
setupPlayer(with: file, driveFileManager: driveFileManager)
}

private func setupPlayer(with file: File, driveFileManager: DriveFileManager) {
if !file.isLocalVersionOlderThanRemote {
player = AVPlayer(url: file.localUrl)
} else if let token = driveFileManager.apiFetcher.currentToken {
driveFileManager.apiFetcher.performAuthenticatedRequest(token: token) { token, _ in
if let token = token {
let url = Endpoint.download(file: file).url
let headers = ["Authorization": "Bearer \(token.accessToken)"]
let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
Task { @MainActor in
self.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
self.addPeriodicTimeObserver()
}
}
}
}
}

private func addPeriodicTimeObserver() {
guard let player = player else { return }
let interval = CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] _ in
self?.updateNowPlayingInfo()
}
NotificationCenter.default.addObserver(self, selector: #selector(playerDidPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)

Check warning on line 70 in kDriveCore/VideoPlayer/VideoPlayer.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Wrap lines that exceed the specified maximum width. (wrap)
}

@objc private func playerDidPlayToEnd() {
onPlaybackEnded?()
}

func setNowPlayingMetadata(playableFileName: String?) {
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.audio.rawValue
nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = false
nowPlayingInfo[MPMediaItemPropertyTitle] = playableFileName

MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}

func stopPlayback() {
player?.pause()
}

private func updateNowPlayingInfo() {
guard let player = player else { return }

var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo

nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentTime())
nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = player.rate

MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
}

0 comments on commit 241750e

Please sign in to comment.