Receiving error while trying to play video with AVPlayer - ios

I am trying to make local video playable with AVPlayer in xcode 10.1, swift 4.2, macOS app. I have created the AVKit Player View object in UI and made outlet in viewController.swift. Also created button in where all the actions should happen. You can see the code here -
import Cocoa
import AVKit
import AVFoundation
import AppKit
class ViewController: NSViewController {
#IBOutlet weak var playerView: AVPlayerView!
#IBAction func buttonAction(_ sender: Any) {
let videoURL = "/Users/ramix/Downloads/test.mp4"
let video = AVPlayer(url: URL(fileURLWithPath: videoURL))
let videoPlayer = AVPlayerView()
videoPlayer.player = video
present(videoPlayer, animator:true, completion:{
video.play()
})
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I saw in multiple places that there was used AVPlayerViewController, but for some reason I dont have option to choose that method. Also this code returns this error -
Cannot invoke 'present' with an argument list of type '(AVPlayerView, animator: Bool, completion: () -> Void)'
I am new to swift, and I would like to ask for your help.
Thank you!

It seems you already created an outlet to an AVPlayerView which you probably added to the view hierarchy in Interface Builder. So you don't need to create a new player view, just assign the player to the outlet view like so:
self.playerView.player = video
Btw, the error happens because present is meant for presenting other view controllers, not for displaying views.

Related

iOS: how to instantiate YTPlayerView programmatically?

I'm trying to instantiate the YTPlayerView from the youtube_ios_player_helper pod programmatically, with the following code:
let player = YTPlayerView()
player.cueVideo(byId: "someId", startSeconds: 0)
then the needed constraints are created and the youtubeplayer view is added to the containerview.
But, the internal webview, remains for ever nil, and is not created. I suppose I'm not instantiating the player correctly, but I did not found any information about how to do it.
Any Help?
thanks
If you look at the source code, cueVideoById does not actually create the internal webview, you need to first call one of the following:
- (BOOL)loadWithVideoId:(NSString *)videoId
- (BOOL)loadWithPlaylistId:(NSString *)playlistId
- (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars
- (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars
You must be creating an object player of YTPlayerView, BUT, it gets deallocated easily. So what to do? Make that a property. Something like this:
private lazy var player: YTPlayerView = {
return YTPlayerView()
}()
override func viewDidLoad()...
or
private var player: YTPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
self.player = YTPlayerView()
}

How to stop background from blocking other elements in Swift 4?

I'm currently making a single view app in Swift 4 / Xcode 10 where there is an animated background and 6 buttons on the screen, with each button linking to a separate ViewController. When I run the app, the animated background seems to move to the front layer and blocks all of the buttons. How do I make it so that the buttons show up in front of the background?
The problem is, I did not create the buttons programatically, but instead dragged and dropped them from the objects library in the interface builder. This is why when I tried the solutions posted here and here, it didn't work, because I don't know if there is any actual code that references any of the buttons. For example, when I tried
view.bringSubview(toFront: theButton)
I wasn't sure what to put in place of theButton, or even where to put that snippet of code.
Below is the code I have on my 'home screen' view controller. My most pressing question is where I would insert code to move either the buttons to the front or the background to the back.
import UIKit
import AVFoundation
import AVKit
class ViewController: UIViewController {
#IBOutlet weak var videoView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "Ocean_Waves_slow_motion_videvo", ofType: "mov")!)
let player = AVPlayer(url: path)
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.videoView.frame
self.videoView.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.videoDidPlayToEnd(_:)), name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"), object: player.currentItem)
}
#objc func videoDidPlayToEnd(_ notification: Notification) {
let player: AVPlayerItem = notification.object as! AVPlayerItem
player.seek(to: CMTime.zero)
}
}
Here is what my Main.storyboard looks like. If it helps, the hierarchy is currently:
View > Video View > View > Button Button Button Button Button Button
1st View -> Video View -> buttonsBgView -> Button Button Button Button Button Button
Don't add the buttonsBgView as subview of Video View. Video View and buttonsBgView should be subview of 1st View.
1st View -> Video View
1st View -> buttonsBgView -> Button Button Button Button Button Button
Create a IBOutlet for the buttonsBgView, and bring that view to front
class ViewController: UIViewController {
#IBOutlet weak var videoView: UIView!
#IBOutlet weak var buttonsBgView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
buttonsBgView.backgroundColor = .clear
view.bringSubview(toFront: buttonsBgView)
}
}

Why my indicator view is not showing?

Using swift3 with xcode8
Below is my viewconroller.swift
class ViewController: UIViewController {
#IBOutlet weak var YahooWebview: UIWebView!
#IBOutlet weak var activity: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let YURL = URL(string: "http://www.yahoo.com")
let YURLRequest = URLRequest(url: YURL!)
YahooWebview.loadRequest(YURLRequest)
}
}
func webViewDidStartLoad(YahooWebview: UIWebView) {
activity.startAnimating()
}
func webViewDidFinishLoad(YahooWebview: UIWebView) {
print("show indicator")
activity.stopAnimating()
}
Why my indicator is not showing when webview is loading?
I can not even see string "show indicator" from my log in Xcode.
You need to set your class as the delegate of UIWebView as
YahooWebview.delegate = self
Check my answer to get details regarding delegates and delegation pattern.
Note from apple developer: In apps that run in iOS 8 and later, use the
WKWebView class instead of using UIWebView. Additionally, consider setting the WKPreferences property javaScriptEnabled to false if you render files that are not supposed to run JavaScript.
There can be two problems as mentioned in comments.
Both can be solved in your Stroyboard/xib file.
Your UIActivityIndicatorView is may be hidden behind UIWebView. Just change the position of the view so that it comes above in the view heirarchy.
You may not have set delegate property of UIWebview class as your ViewController. Right click on webview and check. This needs to be set as you are animating the activity indicator view inside delegate methods.

Place AVPlayerViewController inside UIView

Currently I am having a dialogue view with four controls at the bottom. Each control is loading a different view inside the dialogue. One of these controls is setting an AVPlayer inside the dialogue view and is playing it. Unfortunately the AVPlayer itself comes without playback controls.
The AVPlayerViewController how ever does have playback controls. Is it possible to place a AVPlayerViewController inside a UIView so that it does not get started as a new screen? I would like to place it inside my UIView so that everything is taking place inside my dialgoue.
You can initialize the AVPlayerViewController and add it as a child to your ViewController , and insert it as a subview.
[self addChildViewController:yourPlayerViewController];
[self.view addSubview:yourPlayerViewController.view];
[yourPlayerViewController didMoveToParentViewController:self];
And to remove it:
[yourPlayerViewController willMoveToParentViewController:nil];
[yourPlayerViewController.view removeFromSuperview];
[yourPlayerViewController removeFromParentViewController];
EDIT SWIFT METHOD:
Check here for Swift
I'm using Swift 3.2 / Xcode 9.2 / iOS 11
This is how to do it in 9 easy steps. The steps are explained above each line
**Make sure you import AVKit to have access to the AVPlayerViewController or you won't be able to use it
// 1. to have access to the AVPlayerViewController import AVKit
import AVKit
// 2. create your class properties
var videoUrl: URL?
var player: AVPlayer?
let avPVC = AVPlayerViewController()
override func viewDidLoad() {
super.viewDidLoad()
// 3. make sure the url your using isn't nil
guard let videoUrl = self.videoUrl else { return }
// 4. init an AVPlayer object with the url then set the class property player with the AVPlayer object
self.player = AVPlayer(url: videoUrl)
// 5. set the class property player to the AVPlayerViewController's player
avPVC.player = self.player
// 6. set the the parent vc's bounds to the AVPlayerViewController's frame
avPVC.view.frame = self.view.bounds
// 7. the parent vc has a method on it: addChildViewController() that takes the child you want to add to it (in this case the AVPlayerViewController) as an argument
self.addChildViewController(avPVC)
// 8. add the AVPlayerViewController's view as a subview to the parent vc
self.view.addSubview(avPVC.view)
// 9. on AVPlayerViewController call didMove(toParentViewController:) and pass the parent vc as an argument to move it inside parent
avPVC.didMove(toParentViewController: self)
}
SWIFT 4 - Subview Method
The initial method works alright, but I feel like the view controller method seems messy when I really just want a subview. So, most of the time I play the video in a custom view (rather than using the entire AVPlayerViewController).
I use this method to show the video in a subview which completely fills an existing view. In the example below, urlToVideo is the URL to the video that you want to play, and existingView is the view we want to place our new view into (as a subview).
First, create a VideoPlayerView class in a new VideoPlayerView.swift file, with this, which is directly copy/paste from Apple's website, except that I called it VideoPlayerView rather than PlayerView.
import Foundation
import UIKit
import AVFoundation
/// A simple `UIView` subclass that is backed by an `AVPlayerLayer` layer.
class VideoPlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
Then, when I want to play the video, I create the subview and play it:
let player = AVPlayer(url: urlToVideo)
let playerFrame = CGRect(x: 0, y: 0, width: existingView.frame.width, height: existingView.frame.height)
let videoPlayerView = VideoPlayerView(frame: playerFrame)
videoPlayerView.player = player
existingView.addSubview(videoPlayerView)
player.play()
My solution is to have your view like a canvas and basically draw the AVPlayerViewController on top of it:
#IBOutlet weak var layerVideoView: UIImageView!
guard let url = Bundle.main.path(forResource: "santana", ofType: "mp4") else { return debugPrint("video not found") }
let path = URL(fileURLWithPath: url)
let player = AVPlayer(url: path)
let playerController = AVPlayerViewController()
playerController.player = player
playerController.view.frame = CGRect(x: layerVideoView.frame.origin.x, y: layerVideoView.frame.origin.y, width: layerVideoView.frame.width, height: layerVideoView.frame.height)
self.addChild(playerController)
self.view.addSubview(playerController.view)
LayerVideoView is an image view, but you can use just a UIView if that suits.
Its is important to add constraints in the Storyboard to your View because your Video Player will mimic the shape of your View.

AVPlayerViewController using audio-only AVPlayer

Just starting out with AVKit, and I'm trying to play some audio. It would be nice to use the new AVPlayerViewController detailed in Mastering Modern Media Playback so that I have a ready-made play/pause/seek UI. I basically have a storyboard with a container view which has an embed segue to the AVPlayerViewController. Then I use:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "embedAudio") {
var playerViewController:AVPlayerViewController = segue.destinationViewController as AVPlayerViewController
playerViewController.player = AVPlayer(URL: NSURL(string: "http://dts.podtrac.com/redirect.mp3/files.serialpodcast.org/sites/default/files/podcast/1420057326/serial-s01-e01.mp3"))
}
}
It embeds nicely and plays, but it has a big black QuickTime symbol where the video would be. I'm wondering if there's a way to make it look more like the Music or Podcasts app.
Why reinvent the wheel? Just use AVPlayerViewController's contentOverlayView property. Here you can set an album artwork image, or place any UIView between the QuickTime logo (where the video would be), and the controls.
Note that you must do this after the AVPlayerViewController has been presented, as its contentOverlayView property will be nil in prepareForSegue
Do not use the AVPlayerViewController, as it is a full screen, black box, video player.
Instead, create your own view controller with, say, a toolbar and controls readily available in the OS (play, pause, stop, etc.) and hook them to your AVAudioPlayer. This is what the code of your very own view controller may look like:
Maintain an instance of the player
var audioPlayer:AVAudioPlayer!
#IBOutlet weak var playProgress: UIProgressView!
Example: Play
#IBAction func doPlayAction(_ sender: AnyObject) {
do {
try audioPlayer = AVAudioPlayer(contentsOf: audioRecorder.url)
audioPlayer.play()
} catch {}
}
Example: Stop
#IBAction func doStopAction(_ sender: AnyObject) {
if let audioPlayer = self.audioPlayer {
audioPlayer.stop()
}
}
Example: Track progress
func playerProgress() {
var progress = Float(0)
if let audioPlayer = audioPlayer {
progress = ((audioPlayer.duration > 0)
? Float(audioPlayer.currentTime/audioPlayer.duration)
: 0)
}
playProgress.setProgress(progress, animated: true)
}
I have posted a recording and playback example using the methodology outlined in this answer.
► Find this solution on GitHub.

Resources