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
}
Related
I faced a very strange behaviour of AVPlayerViewController.
I use this code to add play video inside view:
class ViewShowTest:UIViewController
{
let view_for_media = UIView()
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = .red
self.view.addSubview(view_for_media)
view_for_media.snp.makeConstraints(
{ make in
make.top.equalToSuperview().offset(100)
make.left.right.equalToSuperview()
make.height.equalTo(300)
})
setVideo()
}
func setVideo()
{
let url = URL(string: "https://www.radiantmediaplayer.com/media/bbb-360p.mp4")!
let player = AVPlayer(url: url)
let player_vc = AVPlayerViewController(nibName: nil, bundle: nil)
player_vc.player = player
self.view_for_media.addSubview(player_vc.view)
self.addChild(player_vc)
player_vc.didMove(toParent: self)
player_vc.view.frame = view_for_media.bounds
}
}
Video is playing as expected inside view. But after going to fullScreen and returning back (no matter with swipe or close button) ViewShowTest ignores all touches. I still can drag default ios Menus from top and bottom but ViewShowTest view and player_vc.view does not reacts any touches. This bug appears only on ios 12.4 and below. How can i solve this problem?
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 video view set to full screen. However while playing in the simulator, it is not running full screen.
This issue is only for iPads and not iPhones.
Here is my code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
let bundle: Bundle = Bundle.main
let videoPlayer: String = bundle.path(forResource: "normalnewer", ofType: "mp4")!
let movieUrl : NSURL = NSURL.fileURL(withPath: videoPlayer) as NSURL
// var fileURL = NSURL(fileURLWithPath: "/Users/Mantas/Desktop/123/123/video-1453562323.mp4.mp4")
playerView = AVPlayer(url: movieUrl as URL)
NotificationCenter.default.addObserver(self,selector: #selector(playerItemDidReachEnd),name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: self.playerView.currentItem) // Add observer
var playerLayer=AVPlayerLayer(player: playerView)
// self.avPlayerLayer.playerLayer = AVLayerVideoGravityResizeAspectFill
playerLayer.frame = viewVideo.bounds
// self.playerLayer.frame = self.videoPreviewLayer.bounds
viewVideo.layer.addSublayer(playerLayer)
playerView.play()
UserDefaults.standard.set("normalnewer", forKey: "video")
}
I have tried checking landscape mode in target's general.
Gone through the threads below:
how to play a video in fullscreen mode using swift ios?
Play video fullscreen in landscape mode, when my entire application is in lock in portrait mode
Setting device orientation in Swift iOS
But these did not resolve my issue.
Here is a screenshot.
Can somebody help me resolve this?
I see you solved your issue, but I noticed you were using an AVPlayerLayer. The orientation rotation is handled with the AVPlayerViewController, but not with a custom view using a player layer. It is useful to be able to put the layer in fullscreen without rotating the device. I answered this question elsewhere, but I will put it here as well.
Transforms and frame manipulation can solve this issue:
extension CGAffineTransform {
static let ninetyDegreeRotation = CGAffineTransform(rotationAngle: CGFloat(M_PI / 2))
}
extension AVPlayerLayer {
var fullScreenAnimationDuration: TimeInterval {
return 0.15
}
func minimizeToFrame(_ frame: CGRect) {
UIView.animate(withDuration: fullScreenAnimationDuration) {
self.setAffineTransform(.identity)
self.frame = frame
}
}
func goFullscreen() {
UIView.animate(withDuration: fullScreenAnimationDuration) {
self.setAffineTransform(.ninetyDegreeRotation)
self.frame = UIScreen.main.bounds
}
}
}
Setting the frame of the AVPlayerLayer changes it's parent's frame. Save the original frame in your view subclass, to minimize the AVPLayerLayer back to where it was. This allows for autolayout.
IMPORTANT - This only works if the player is in the center of your view subclass.
Incomplete example:
class AVPlayerView: UIView {
fileprivate var avPlayerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
fileprivate var hasGoneFullScreen = false
fileprivate var isPlaying = false
fileprivate var originalFrame = CGRect.zero
func togglePlayback() {
if !hasGoneFullScreen {
originalFrame = frame
hasGoneFullScreen = true
}
isPlaying = !isPlaying
if isPlaying {
avPlayerLayer.goFullscreen()
avPlayerLayer.player?.play()
} else {
avPlayerLayer.player?.pause()
avPlayerLayer.minimizeToFrame(originalFrame)
}
}
}
After spending some time taking a good look at the Storyboard, I tried changing the fill to Aspect fill & fit and that resolved the problem :)
The best thing you can do is put this code
In viewDidLayoutSubviews and enjoy it!
DispatchQueue.main.async {
self.playerLayer?.frame = self.videoPlayerView.bounds
}
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.
what is the most efficient way to add a GIF/Video to the background of the landing screen ( home screen or first view controller) of my app in Xcode? i.e apps like spotify, uber, insagram etc. Being that my app is universal, how would i make it fit accordingly?
Do you mean the first screen that is displayed after your app is launched? If so: unfortunately you can't have dynamic content; you won't be able to use a gif/video.
That said, what you can do if you have some app-setup on background threads that will take some time anyway, or if you simply want the user to wait longer before interaction so that you can display the gif/video, you can make the static image match the first frame of the gif/video, and have your your entry point be a ViewController that displays the actual gif/video. Because this would delay the time to interaction, though, this would never be recommended.
As for making it fit: as of iOS 8 Apple recommends using LaunchScreen.xib. With it you can use Auto Layout to achieve universality.
To add a video you can use MPMoviePlayerController, AVPlayer, or if you're using SPritekit you can use an SKVideoNode.
EDIT (in response to follow-up comments):
An NSURL is a reference to a local or remote file. This link will give you a decent overview. Just copy the movie in and follow that guide.
In addition to the MPMoviePlayerController solution Saqib Omer suggested, here's an alternative method that uses a UIView with an AVPlayerLayer. It has a button on top of the video as an example, since that's what you're looking for.
import AVKit
import AVFoundation
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Start with a generic UIView and add it to the ViewController view
let myPlayerView = UIView(frame: self.view.bounds)
myPlayerView.backgroundColor = UIColor.blackColor()
view.addSubview(myPlayerView)
// Use a local or remote URL
let url = NSURL(string: "http://eoimages.gsfc.nasa.gov/images/imagerecords/76000/76740/iss030-e-6082_sdtv.mov") // See the note on NSURL above.
// Make a player
let myPlayer = AVPlayer(URL: url)
myPlayer.play()
// Make the AVPlayerLayer and add it to myPlayerView's layer
let avLayer = AVPlayerLayer(player: myPlayer)
avLayer.frame = myPlayerView.bounds
myPlayerView.layer.addSublayer(avLayer)
// Make a button and add it to myPlayerView (you'd need to add an action, of course)
let myButtonOrigin = CGPoint(x: myPlayerView.bounds.size.width / 3, y: myPlayerView.bounds.size.height / 2)
let myButtonSize = CGSize(width: myPlayerView.bounds.size.width / 3, height: myPlayerView.bounds.size.height / 10)
let myButton = UIButton(frame: CGRect(origin: myButtonOrigin, size: myButtonSize))
myButton.setTitle("Press Me!", forState: .Normal)
myButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
myPlayerView.addSubview(myButton)
}
}
For playing video add following code, declare class variable var moviePlayer:MPMoviePlayerController! . Than in your viewDidLoad()
var url:NSURL = NSURL(string: "YOUR_URL_FOR_VIDEO")
moviePlayer = MPMoviePlayerController(contentURL: url)
moviePlayer.view.frame = CGRect(x: 0, y: 0, width: 200, height: 150)
self.view.addSubview(moviePlayer.view)
moviePlayer.fullscreen = true
moviePlayer.controlStyle = MPMovieControlStyle.Embedded
This will play video. But to fit it you need to add layout contraints. See this link to add constraints pragmatically.
import MediaPlayer
class ViewController: UIViewController {
var moviePlayer: MPMoviePlayerController!
override func viewDidLoad() {
super.viewDidLoad()
// Load the video from the app bundle.
let videoURL: NSURL = NSBundle.mainBundle().URLForResource("video", withExtension: "mp4")!
// Create and configure the movie player.
self.moviePlayer = MPMoviePlayerController(contentURL: videoURL)
self.moviePlayer.controlStyle = MPMovieControlStyle.None
self.moviePlayer.scalingMode = MPMovieScalingMode.AspectFill
self.moviePlayer.view.frame = self.view.frame
self.view .insertSubview(self.moviePlayer.view, atIndex: 0)
self.moviePlayer.play()
// Loop video.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loopVideo", name: MPMoviePlayerPlaybackDidFinishNotification, object: self.moviePlayer)
}
func loopVideo() {
self.moviePlayer.play()
}
https://medium.com/#kschaller/ios-video-backgrounds-6eead788f190#.2fhxmc2da