I'm attempting to enable AirPlay with my AVPlayerViewController. In the document:
https://developer.apple.com/reference/avkit/avplayerviewcontroller
It states
AVPlayerViewController automatically supports AirPlay, but you need to perform some project and audio session configuration before it can be enabled in your application.
Under the Capabilities tab, I did enable the Background Mode for Audio, AirPlay, and Picture in Picture. I have created the AVPlayerViewController as follows:
// Create the view controller and player
let moviePlayerViewController: AVPlayerViewController = AVPlayerViewController()
let moviePlayer = AVPlayer(url: videoUrl!)
moviePlayer.allowsExternalPlayback = true
moviePlayer.usesExternalPlaybackWhileExternalScreenIsActive = true
// Initialize the AVPlayer
moviePlayerViewController.player = moviePlayer
// Present movie player and play when completion
self.present(moviePlayerViewController, animated: false, completion: {
moviePlayerViewController.player?.play()
})
I thought the two lines
moviePlayer.allowsExternalPlayback = true
moviePlayer.usesExternalPlaybackWhileExternalScreenIsActive = true
Would add the AirPlay support but I'm wrong. I have read that AirPlay can be used by adding MPVolumeView, but that's for a custom video controller, not the built in one. Any help would be greatly appreciated.
You should still be able to use MPVolumeView. Just use the following:
let volumeView = MPVolumeView()
self.view.addSubview(volumeView)
or if you just want the menu to show up, you can use:
let volumeView = MPVolumeView(frame: CGRect(x: -100, y: 0, width: 0, height: 0))
self.addSubview(volumeView)
for view: UIView in volumeView.subviews {
if let button = view as? UIButton {
button.sendActions(for: .touchUpInside)
volumeView.removeFromSuperview()
break
}
}
This will put it outside of the current view and then trigger the action that displays the air play menu.
Nowadays you should have no problem doing this.
(1) Under the Capabilities tab, enable Background Mode for Audio
(2) With your
var ezPlayer: AVPlayerViewController!
just
ezPlayer.showsPlaybackControls = true
ezPlayer.player = AVPlayer(url: ... )
ezPlayer.player!.allowsExternalPlayback = true
ezPlayer.player!.usesExternalPlaybackWhileExternalScreenIsActive =
and you should be fine. Build to a device. Assuming you are on a usable wifi network with an apple TV, apple laptops, the airplay icon will appear and work.
There's no need to use an MPVolumeView these days.
Related
I am using MPMusicPlayerController.applicationQueuePlayer to control to play internal music.
extension MPVolumeView {
var volumeSlider: UISlider? {
showsRouteButton = false
showsVolumeSlider = false
isHidden = true
for subview in subviews where subview is UISlider {
let slider = subview as! UISlider
slider.isContinuous = false
slider.value = AVAudioSession.sharedInstance().outputVolume
return slider
}
return nil
}
}
let player = MPMusicPlayerController.applicationQueuePlayer
player.shuffleMode = .off
player.setQueue(with: MPMediaItemCollection(items: [mediaItem]))
player.play()
let volView = MPVolumeView()
view.addSubview(volView)
UIApplication.shared.keyWindow?.insertSubview(volView, at: 0)
volView.frame.origin.x = -1000
/* I also tried:
MPVolumeView(frame: .zero)
MPVolumeView(frame: CGRect(x: -1000, y: -1000, width: 0, height: 0))
none of them works.
*/
// get the slider to change to volume
let slider = volView.volumeSlider!
// set the volume. when I change, the volume HUD appears. but I want to stop appearing.
slider.setValue(1, animated: false)
I want to know when changing the volume, how can I stop the volume HUD popping up?
EDITED.
System Volume is user experience
any way you can use this
UIApplication.shared.keyWindow?.insertSubview(MPVolumeView(), at: 0)
First, note that digging around in the MPVolumeView this way is completely unsupported, and there is no promise that it will work in future versions of iOS. Apple intentionally does not provide a way to change the master volume. It is exactly the kind of thing that may be moved into a separate process in the future. Apple has been moving a lot of these kinds of things into separate processes.
(I say all this because I'm in the same boat with a product that does about the same thing, and I've been working for over a year to devise a new solution that does not require modifying the volume. You don't want to do this unless it is critical to the product.)
To your actual question, move the view off-screen:
if let window = UIApplication.shared.windows.first {
let volView = MPVolumeView()
volView.frame = CGRect(x: -window.frame.size.width,
y: -window.frame.size.height,
width: window.frame.size.width,
height: window.frame.size.height)
window.addSubview(volView)
}
I am building a screen where users can play audio files using an AVPlayerViewController. The problem is that I can't get rid of the QuickTime logo in the player view, see screenshot:
My code:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
guard let url = URL(string: filePath!) else {
return
}
let player = AVPlayer(url: url)
let controller = AVPlayerViewController()
controller.modalPresentationStyle = .overFullScreen
controller.player = player
self.present(controller, animated: true) {
player.play()
}
} catch {
}
I've tried adding an UIImageView using controller.contentOverlayView?.addSubView(), but I can't center that properly. How do I customize the layout of the player without having to build my own interface from scratch?
What you tried is correct: just add a subview to the content overlay view. But you left out one step: you must give both the subview and the content overlay view constraints, to make them cover the player controller’s view completely.
Example (my av is your controller):
let iv = UIView()
iv.backgroundColor = .white
av.contentOverlayView!.addSubview(iv)
let v = av.contentOverlayView!
iv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
iv.bottomAnchor.constraint(equalTo:v.bottomAnchor),
iv.topAnchor.constraint(equalTo:v.topAnchor),
iv.leadingAnchor.constraint(equalTo:v.leadingAnchor),
iv.trailingAnchor.constraint(equalTo:v.trailingAnchor),
])
NSLayoutConstraint.activate([
v.bottomAnchor.constraint(equalTo:av.view.bottomAnchor),
v.topAnchor.constraint(equalTo:av.view.topAnchor),
v.leadingAnchor.constraint(equalTo:av.view.leadingAnchor),
v.trailingAnchor.constraint(equalTo:av.view.trailingAnchor),
])
If all you want to do is remove the "Q" logo without replacing it with anything, you can get rid of this way:
(avPlayerViewController?.view.subviews.first?.subviews.first as? UIImageView)?.image = nil
But, at least for me, the "Q" logo usually only appears about 2 seconds after I've set up the player view controller. So you might need to use a timer to get the above code to run about 2 seconds after creating the player.
This question already has answers here:
AVPlayerViewController using audio-only AVPlayer
(2 answers)
Closed 6 years ago.
I am using an AVPlayerViewController to play an audio stream, on tvOS, and I am now wanting to have some associated artwork displayed at the same time.
I am not sure whether I should be somehow overlaying it on top or be telling the AVPlayerViewController about the artwork. If it is the latter I can't seem to see the right way to tell the AVPlayerViewController about it? Does anyone have indications as to an appropriate approach?
class StreamPlayerViewController: AVPlayerViewController
var playerItem:AVPlayerItem?
override func viewDidLoad() {
let videoURL = NSURL(string: "http://example.org/aac.m3u")
playerItem = AVPlayerItem(URL: videoURL!)
self.player=AVPlayer(playerItem: playerItem!)
let playerLayer=AVPlayerLayer(player: player)
playerLayer.frame=CGRectMake(0, 0, 300, 50)
self.view.layer.addSublayer(playerLayer)
// TODO add art work or other metadata, not coming from stream
// TODO Tell player stream is of infinite time
self.player?.play()
}
}
Following off the comment provided by #JAL (original code), is the following code:
override func viewWillAppear(animated: Bool) {
let image = UIImage(named: "stationAlbumArt")
let albumArtView = UIImageView(image: image)
// ensure view is screen size
albumArtView.frame = UIScreen.mainScreen().bounds
// set bounds, so that image stretch to fill
albumArtView.bounds = CGRectMake(0, 0, image!.size.width, image!.size.height)
self.contentOverlayView?.addSubview(albumArtView)
}
The two key additions here are setting the frame and the bounds. The frame here is using the screen size, since self.view.frame is not available until the viewDidAppear() is called. The bounds need to be set to ensure the image doesn't stretch to fill the screen.
One other thing, while the AVPlayerLayer needs to be instantiated it doesn't actually seem to need to be added as a layer, at least based on experimentation.
The only thing missing at this point is the ability to mask the time bar, without losing the audio control menu.
I'm trying to set the video frame of an AVPlayer so that it takes like 75% of the screen size and let the background view visible using transparency.
However, I couldn't manage to set the size of my new VC, it always takes the whole width.
Here is my code :
import AVKit
import AVFoundation
class VideoPlayer {
// ----------------------------------------------------------------------------------
// MARK: - Properties
// ----------------------------------------------------------------------------------
var videoName = "here video url"
let playerViewController = AVPlayerViewController()
// ----------------------------------------------------------------------------------
// MARK: - Setup
// ----------------------------------------------------------------------------------
func setupAndPlayVideo(sourceViewController: UIViewController) {
// Get video URL
let videoURL = urlForFile(videoName)
let player = AVPlayer(URL: videoURL!)
// Grey transparent background
playerViewController.view.backgroundColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 0.5)
// Prevent user from pausing/dismissign the video
playerViewController.showsPlaybackControls = false
// Present the video player VC and play video
playerViewController.player = player
// This does not change anything (sample hard coded values)
playerViewController.view.frame = CGRectMake(0, 0, 300, 300)
sourceViewController.presentViewController(playerViewController, animated: true) {
self.playerViewController.player!.play()
}
}
}
EDIT
So I decided to change my strategy and add an AVPlayerViewController in my storyboard.
Here is the code I use to play my video :
// Game over
case PapooSegue.GameEndSegue.rawValue:
// Get photo VC
let videoVC = segue.destinationViewController as! AVPlayerViewController
// Get video URL and provide it to the player
let videoName = papooBrain!.getGameOverVideoName()
let videoURL = urlForFile(videoName)
videoVC.player = AVPlayer(URL: videoURL!)
// Set grey/transparent background
videoVC.view.backgroundColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.6)
// Play video
videoVC.player?.play()
The problem is still the same. I tried to play with the AVPlayerLayer as suggested in this post, but I ended up with 2 videos playing :
The one that is stretched by videoGravity I guess
The one with a custom frame
I don't know how I can have only one on these playing with the appropriate size.
Correct me if I'm wrong but it looks like you're using the VideoPlayer class in a different view controller. You should be able to control the frame from that VC, not the VideoPlayer class. So if you go to the VC containing the VideoPlayer create the videoPlayer var -
Example: var videoPlayer : VideoPlayer!
then in your viewdidload you should be able to call your function. by saying
videoPlayer.setUpAndPlayVideo
Hope this helps.
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