AVPlayer Swift: How do I hide controls and disable landscape view? - ios

since this is my first post, just a few words about me: Usually I design stuff (UI primarily) but I really wanted to take a leap into programming to understand you guys better. So I decided build a small app to get going.
So I've been trying to figure this out for hours now – this is my first app project ever so I apologise for my newbyness.
All I want to do is to hide the controls of AVPlayer and disable landscape view but I just can't figure out where to put showsPlaybackControls = false.
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
private var firstAppear = true
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if firstAppear {
do {
try playVideo()
firstAppear = false
} catch AppError.InvalidResource(let name, let type) {
debugPrint("Could not find resource \(name).\(type)")
} catch {
debugPrint("Generic error")
}
}
}
private func playVideo() throws {
guard let path = NSBundle.mainBundle().pathForResource("video", ofType:"mp4") else {
throw AppError.InvalidResource("video", "m4v")
}
let player = AVPlayer(URL: NSURL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.player = player
self.presentViewController(playerController, animated: false) {
player.play()
}
}
}
enum AppError : ErrorType {
case InvalidResource(String, String)
}

showsPlaybackControls is a property of AVPlayerViewController, so you can set it after you create it:
playerController.showsPlaybackControls = false
If you want to allow only landscape, you can subclass AVPlayerViewController, override supportedInterfaceOrientations and return only landscape, then use that class instead of AVPlayerViewController.
UPDATE
As mentioned in the comments, if you go to the documentation for AVPlayerViewController you'll find a warning that says:
Do not subclass AVPlayerViewController. Overriding this class’s methods is unsupported and results in undefined behavior.
They probably don't want you to override playback-related methods that could interfere with the behavior, and I would say that overriding only supportedInterfaceOrientations is safe.
However, if you want an alternative solution, you can create your own view controller class that overrides supportedInterfaceOrientations to return landscape only, and place the AVPlayerViewController inside that view controller.

Related

Looping an iOS live photo programmatically in SwiftUI

I'd like to be able to loop a live photo, for continuous playback.
So far, I'm trying to use the PHLivePhotoViewDelegate to accomplish this.
import Foundation
import SwiftUI
import PhotosUI
import iOSShared
struct LiveImageView: UIViewRepresentable {
let view: PHLivePhotoView
let model:LiveImageViewModel?
let delegate = LiveImageLargeMediaDelegate()
init(fileGroupUUID: UUID) {
let view = PHLivePhotoView()
// Without this, in landscape mode, I don't get proper scaling of the image.
view.contentMode = .scaleAspectFit
self.view = view
// Using this to replay live image repeatedly.
view.delegate = delegate
model = LiveImageViewModel(fileGroupUUID: fileGroupUUID)
guard let model = model else {
return
}
model.getLivePhoto(previewImage: nil) { livePhoto in
view.livePhoto = livePhoto
}
}
func makeUIView(context: Context) -> UIView {
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let model = model else {
return
}
guard !model.started else {
return
}
model.started = true
view.startPlayback(with: .full)
}
}
class LiveImageLargeMediaDelegate: NSObject, PHLivePhotoViewDelegate {
func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
livePhotoView.stopPlayback()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) {
livePhotoView.startPlayback(with: .full)
}
}
}
But without full success. It seems the audio does play again, but not the video. The livePhotoView.stopPlayback and the async aspect are just additional changes I was trying. I've tried it without those too.
Note that I don't want the user to have to manually change the live photo (e.g., see NSPredicate to not include Loop and Bounce Live Photos).
Thoughts?
ChrisPrince I tried your code and it works fine for me, I just add delegate and start playback inside of it and everything runs well and smoothly. I thought that there is no point in using stop playback because the function itself says that the playback ended.
func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
livePhotoView.startPlayback(with: .full)
}

`audioPlayerDidFinishPlaying` Not Being Called [duplicate]

This question already has an answer here:
AVAudioPlayer.play() does not play sound
(1 answer)
Closed 3 years ago.
Having an issue where audioPlayerDidFinishPlaying isn't getting called...
I've looked at some other SO threads:
Swift: AudioPlayerDidFinished will not be called
AVAudioPlayerDelegate doesn't call the method
...but still having the same issue.
I've set my ViewController to conform to AVAudioPlayerDelegate:
class ViewController: UIViewController, AVAudioPlayerDelegate {
var audioEngine = AVAudioEngine()
...
}
And create the player here; setting the delegate right after creating the player itself:
func startAudioEngine(audioFileURL: URL) {
var player: AVAudioPlayer?
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: audioFileURL, fileTypeHint: AVFileType.mp3.rawValue)
guard let player = player else { return }
player.delegate = self
player.prepareToPlay()
player.play()
} catch let error {
print(error.localizedDescription)
}
}
Your problem is twofold:
var player: AVAudioPlayer? is declared local to func startAudioEngine(), so as soon as the function exists, you lose reference to player.
You are setting the delegate on the optionally bound player
Change to this (or something like this):
class ViewController: UIViewController, AVAudioPlayerDelegate {
var audioEngine = AVAudioEngine()
var player: AVAudioPlayer?
}
Then after you assign player, declare your delegate self.player?.delegate = self. Make sure to include self. so that the delegate is set on the class property and not the optional binding;

AdMob - Get Notify when isReady property changes

I trying implement Rewarded Ad - Rewarded Ads New APIs (Beta). Video is load and isReady property is changing to true in a couple of seconds.
I have a button on which user press and Rewarded Video appear
This is function which is fire when user press on button
func presentRewardAd(from viewController: UIViewController) {
if rewardedAd.isReady {
rewardedAd.present(fromRootViewController: viewController, delegate: self)
}
}
The problem is
I want to hide button until video isReady == true, and when it's ready show button. So i want to get notify when rewardedAd.isReady is changing.
What i try so far:
class CustomRewardAd: GADRewardedAd {
private var observation: NSKeyValueObservation?
override init(adUnitID: String) {
super.init(adUnitID: adUnitID)
observation = observe(\.isReady, options: [.old, .new]) { object, change in
print("isReady changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}
}
}
Also i tried this Using Key-Value Observing in Swift but same result.
But changeHandler never gets called. Am i doing something wrong?
Thanks!
I found solution, not ideal but it's works! Maybe this is can help someone in future.
When new rewarded request finishes, isReady property is set to true or false depends what response is.
private func createAndLoadRewardedAd() -> GADRewardedAd {
let rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
rewardedAd.load(GADRequest()) { [weak self] error in
guard let self = self else { return }
self.videoIsReady?(rewardedAd.isReady) // already set
}
return rewardedAd
}
You are welcome!

AVPlayerViewController show black screen some times

I create an player and working fine in most part of times.
In some situations (that I didn't realized why) screen video stays black with a play button that does nothing.
I verified url and is ok, that's not the problem.
In my viewController I can call this block of code multiples times with different urls, that's why I 'restart' AVPlayerViewController.
// Create an var in class...
// ....
self.videoPlayerViewController?.player?.pause()
self.videoPlayerViewController = AVPlayerViewController()
self.videoPlayerViewController?.player = viewModel.avPlayer
if let avController = self.videoPlayerViewController {
self.add(avController, in: self.playerView)
avController.player?.play()
} else {
// Error
}
That's function add:
extension UIViewController {
func add(_ viewController: UIViewController, in view: UIView) {
viewController.view.frame = view.bounds
addChildViewController(viewController)
view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
view.clipsToBounds = true
}
}
Someone knows what is wrong?
Thanks in advance!!
After so many time.. I found solution.
The problem was that I wasn't cleaning AVPlayer inside AVPlayerController. And I also added New instance inside a DispachQueue.
That's new code:
self.videoPlayerViewController?.player?.pause()
self.videoPlayerViewController?.player = nil
self.videoPlayerViewController = nil
self.videoPlayerViewController = AVPlayerViewController()
self.videoPlayerViewController?.player = viewModel.avPlayer
And after I added in viewController:
if let avController = self.videoPlayerViewController {
DispatchQueue.main.async { [weak self] in
if let strongSelf = self {
strongSelf.add(avController, in: strongSelf.playerView)
avController.player?.play()
}
}
} else {
// Error
}
I hope it could help someone!!

Swift need closure for audioplayer

I am building an app with several view controllers. I need to control music from all of them so I have created a dedicated music class which I use to setup / play / stop / pause.
I have recorded audio questions and answers and I need to be able to play the question and then the answer mp3 files.
So I believe that here a few way to accomplish this delegate and protocols, using the func audioPlayerDidFinishPlaying and using closures. From what I can understand closures are the best option for what I am trying to achieve.
My starting point in the MakeMusic Class is:
class MakeMusicClass : NSObject, AVAudioPlayerDelegate {
static let shared = MakeMusicClass()
var audioPlayer = AVAudioPlayer()
override init() { }
func setup(Selection: String) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: Selection, ofType: "mp3")!))
audioPlayer.prepareToPlay()
audioPlayer.delegate=self
} catch {
print (error)
}
}
func play() {
audioPlayer.play()
}
My calling file is:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
MakeMusicClass.shared.setup(Selection: "Question11")
MakeMusicClass.shared.play()
MakeMusicClass.shared.setup(Selection: "Answer11")
MakeMusicClass.shared.play()
To get this working I understand that I need to add a closure to the called class:
func play() {
var closure = { in
audioPlayer.play()
}
}
And I need to update where I need to call the function something like:
override func viewDidLoad() {
super.viewDidLoad()
MakeMusicClass.shared.setup(Selection: "Question11")
MakeMusicClass.shared.play() {
MakeMusicClass.shared.setup(Selection: "Answer11")
MakeMusicClass.shared.play()
}
I have spent ages trying to get my head around this, but I am struggling. My code here is clearly not working as there is something fundamental that I am missing. I have tried passing void and parameters, but I don't understand what parameters should be passed. The closest I have come is using the audioPlayerDidFinishPlaying within the makemusic class trigger the next audio file, but I don't know that this is ideal.
I think your best bet will be in your MakeMusicClass taking an Array as an initialiser e.g. a question and and answer, and then use AVAudioPlayerDelegate to trigger the next file, which I think is what you are trying to get at (and very close ^________*).
For example:
class AudioLooper: NSObject, AVAudioPlayerDelegate {
var debugView = true
var audioLoopPlayer: AVAudioPlayer!
var audioFileIndex: Int = 0
var audioFileArray: [String] = []
//-------------------------------------------------
//MARK: Audio Player Initialisation & Functionality
//-------------------------------------------------
/// Function To Initialise The Audio Loop Player
///
/// - Parameter audioFiles: [String] - The Array Of Audio Files To Play
func initAudioPlayerWith(audioFiles: [String]) {
audioFileArray = audioFiles
if debugView { print("Audio Files To Play In Sequence == \(audioFileArray)") }
}
/// Function To Play An Array Of Audio Files
///
/// - Parameter index: (Int) - The Current Index Of The Audio Sequence
func playAudioLoopAt(index: Int) {
let currentAudioFile = "\(audioFileArray[audioFileIndex])"
if let audioFilePath = Bundle.main.path(forResource: currentAudioFile, ofType: "mp3"){
do {
let audioFileUrl = NSURL.fileURL(withPath: audioFilePath)
// Set An Instance Of AVAudioSession Which Allows Playback Even When Device Is Muted
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
audioLoopPlayer = try AVAudioPlayer(contentsOf: audioFileUrl)
audioLoopPlayer.prepareToPlay()
audioLoopPlayer.delegate = self
audioLoopPlayer.play()
if debugView { print("Playing \(currentAudioFile) ") }
audioFileIndex += 1
} catch {
print("Error Playing Audio")
}
} else {
print("Error Finding File: \(currentAudioFile)")
}
}
/// Function To Continue The Audio Sequence
#objc func continueAudioLoop(){
playAudioLoopAt(index: audioFileIndex)
}
/// Function To Stop The Audio Looop Player
func stopAudioPlayer() {
if audioLoopPlayer != nil {
audioFileIndex=0
audioLoopPlayer.stop()
audioLoopPlayer = nil
}
}
//-----------------------------
//MARK: AVAudio Player Delegate
//-----------------------------
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if audioFileIndex < audioFileArray.count {
self.perform(#selector(continueAudioLoop), with: self, afterDelay: 0.5)
}else{
audioFileIndex=0
}
}
}
In my example you simply init like so:
audioLooper = AudioLooper()
audioLooper?.initAudioPlayerWith(audioFiles: ["question", "answer"])
audioLooper?.playAudioLoopAt(index: 0)
Where audioLooper is declared like so : var audioLooper:AudioLooper?
Clearly my example is not a Singleton, but it should give you an idea of how you can adjust your MakeMusicClass to make it fit...
You can also add a Delegate Method like so to inform the ViewController the audio has finished or perform some other task like update the next question etc e.g:
#objc protocol AudioLooperDelegate {
#objc optional func update()
}
Then in your ViewController:
var delegate: AudioLooperDelegate?
And in the AudioLooper Class you can then add the delegate method where needed e.g:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if audioFileIndex < audioFileArray.count {
self.perform(#selector(continueAudioLoop), with: self, afterDelay: 0.5)
}else{
audioFileIndex=0
delegate?.updateUI!()
}
}
}

Resources