How to play a local video in a background view in ios - ios

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".

Related

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)
}
}

Videos in welcome screen using AVPlayer causing memory leak?

In my app, there is a welcome screen consist of 3 screens. They all inherit from one super class. The different between them are just texts and video url:
|-SuperWelcomeScreenViewController
|----FirstWelcomeScreenViewController
|----SecondWelcomeScreenViewController
|----ThirdWelcomeScreenViewController
In the super class, I have a view to load view:
private final var player: AVPlayer = AVPlayer()
private final lazy var videoView: UIView = {
let v = UIView()
let videoString:String? = Bundle.main.path(forResource: self.videoPath, ofType: "mp4")
guard let unwrappedVideoPath = videoString else {return v}
let videoUrl = URL(fileURLWithPath: unwrappedVideoPath)
let item = AVPlayerItem(url: videoUrl)
self.player.replaceCurrentItem(with: item)
let layer: AVPlayerLayer = AVPlayerLayer(player: player)
//Using the size of the video
layer.frame = CGRect(x: -125, y: 0, width: 250, height: 541)
layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
v.layer.addSublayer(layer)
return v
}()
Here is the issue: I test the memory usage on Simulator, in the beginning it's about 250MB. Then I swiped to next welcome screen, it became 450MB. Then next for 550MB. My welcome screen has a infinite loop, but the usage stays at maximum after 3 viewcontrollers are all showed.
Then I went to the login screen, and then went back to welcome screen. The initial usage became 750MB. If I keep jumping from the login screen and the welcome screen, the usage will keep raising!
Here are what I want to do:
First, I want memory usage stay steady when I jumping from the login screen and the welcome screen.
Second, I want recycle memory between welcome screens. So that in every screen the usage would roughly be 250MB.
Here are what I have tried and failed:
Change player to static
Remove AVPlayerLayer from superLayer when view willDisappear
Set AVPlayer to nil
FYI I ran CFGetRetainCount in viewDidLoad and it's 5. I cannot find the other 4.
Actually doing things below together did the trick
Remove AVPlayerLayer from superLayer when view willDisappear
Set AVPlayer to nil

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.

Set size for Video with AVPlayerViewController

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()

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