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.
Related
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.
I have a UIViewcontroller that contains a UIView and a button widget. Here is an image of the viewcontroller class
Image of my main VC
I'm trying to play a video on the uiview(like in background) and use the button to stop or play the video but the thing is when I press the play button the video comes(redeners) on top the of the button and I can see and unpause it(well it should restart becuase I haven't written the code to handle that)
here is my ibaction function's code associated with that button:
#IBOutlet weak var videoView: UIView!
#IBAction func playButtonPressed(_ sender: UIButton)
{
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "color spiral", ofType: "mov")!)
let player = AVPlayer(url: path)
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.videoView.bounds
self.videoView.superview?.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
}
this is what I get when I press the button:
image of simulator after i press the button
so how do I play the video but not over my button or keep the button showing?
Try dragging the [button] in Document Outline (left panel of Interface Builder) outside of "video view" and into the "view".
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.
I need to play a short video at the end of my game.
I created an AVPlayerViewController on my storyboard with a modal presentation using a segue from the previous View Controller.
However I want the video NOT to cover the whole screen. Let's say 50% (centered) of the screen size.
Here is the code I tried :
import AVKit
import AVFoundation
import UIKit
class VideoPlayerViewController: AVPlayerViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
// Play video
player?.play()
}
override func viewDidLayoutSubviews() {
print(videoBounds)
self.view.bounds = videoBounds
}
}
The problem is that the video scales to the whole screen is very small (like something around 100*70px). I believe the default size is 100*100 at some point and it scales it to match my video ratio.
If I don't specify bounds, it scales to the whole screen (video gravity don't let me the choice).
If I reduce the size on my whole view in viewDidLayoutSubviews, it's still not okay because this method is called multiple times so I keep changing my view bounds.
I can't believe how hard it is to display a local video on part of the screen with AVPlayerViewController...
Any help ?
You can make the view controller any size you'd like. Like this, if you wanted to:
let videoURL = URL(fileURLWithPath: videoPath)
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.view.frame = CGRect (x:100, y:100, width:200, height:100)
playerViewController.player = player
self.addChildViewController(playerViewController)
self.view.addSubview(playerViewController.view)
playerViewController.didMove(toParentViewController: self)
(obviously, the above size would be weird, but you can. if you want to).
Use the ViewController if you want automatically managed player controls. If you want to roll your own controls, the layer is probably better.
Just posting this to correct the answer that indicated you shouldn't use the VC except in full screen. Seems to work fine for me.
AVPlayerViewController is only meant to be used in a full screen manner, you are better off using AVPlayerLayer this allows you to size / place it in a view of your choosing.
let player = AVPlayer(url: myURL)
let layer = AVPlayerLayer(player: player)
layer.frame.size = CGSize(100,100)
layer.frame.origin = myView.center
player.play()
I try to play a video using a MPMoviePlayerController. The setup is: I push a new ViewController, then set up the view and the movie player instance in viewDidLoad and then use a NSURLSession.sharedSession().dataTaskWithURL() where I load the REST resource for the movie to give me the URL. In the completion block I set the contentUrl of the movie player instance to this url and say play. However, the movie frame stays black. If I set the contentUrl hardcoded to the url either in viewDidLoad, viewWillAppear, or viewDidAppear, the movie shows just fine.
The errorLog and accessLog are both nil.
So I assume something is wrong with the asynchronous url loading and assigning of the movie contentUrl.
Setup: Swift, Xcode 6 beta, iOS 8.
Below some code snippets:
class PresentationsViewController {
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let presentationViewController = PresentationViewController(presentations[indexPath.row])
navigationController.pushViewController(presentationViewController, animated: true)
}
}
class PresentationViewController {
var presentation: Presentation?
var moviePlayer: MPMoviePlayerController?
convenience init(_ presentation: Presentation) {
self.init()
self.presentation = presentation
}
override func viewDidLoad() {
super.viewDidLoad()
moviePlayer = MPMoviePlayerController()
moviePlayer!.view.frame = CGRect(x: X, y: Y, width: W, height: H)
moviePlayer!.movieSourceType = MPMovieSourceType.Unknown
moviePlayer!.controlStyle = MPMovieControlStyle.Embedded
NSURLSession.sharedSession().dataTaskWithURL(presentation.url) { data, response, error in
// Some JSON parsing etc.
self.moviePlayer!.contentURL = presentation.videoUrl
self.moviePlayer!.prepareToPlay()
self.moviePlayer!.play()
}.resume()
view.addSubview(moviePlayer.view)
}
}
I am not sure if this was a bug in the Swift betas or iOS 8 betas, but changing the code to use AVPlayer worked.
import AVFoundation
import AVKit
let playerViewController = AVPlayerViewController()
// In async block:
if let player = AVPlayer.playerWithURL(url) as? AVPlayer {
playerViewController.player = player
}