John Jackson
John Jackson

Reputation: 860

How do I keep my video background to continuously play using swift?

I have a video playing as the background of my landing viewcontroller and anytime I exit the app then re-enter, the video freezes, until I either go to a different view and come back, or just restart the app all together will it play normally. How do I get it to remain playing even if the app is exited and re-entered?

Here is the logic:

import UIKit
import AVFoundation
class FirstViewController: UIViewController {

    var player: AVPlayer?

    override func viewDidLoad() {
        super.viewDidLoad()
        let path = NSBundle.mainBundle().pathForResource("sunny", ofType: "mp4")
        player = AVPlayer(URL: NSURL(fileURLWithPath: path!))
        player!.actionAtItemEnd = AVPlayerActionAtItemEnd.None;
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = self.view.frame
        playerLayer.videoGravity = AVLayerVideoGravityResize
        self.view.layer.insertSublayer(playerLayer, atIndex: 0)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: AVPlayerItemDidPlayToEndTimeNotification, object: player!.currentItem)
        player!.seekToTime(kCMTimeZero)
        player!.play()

    }

    func playerItemDidReachEnd() {
        player!.seekToTime(kCMTimeZero)
    }

}

Upvotes: 2

Views: 2609

Answers (3)

Harman
Harman

Reputation: 446

Swift 4 In AppDelegate.swift

    func applicationDidEnterBackground(_ application: UIApplication) {
    if let item = yourAVPlayer.currentItem {
        if item.tracks.first!.assetTrack.hasMediaCharacteristic(AVMediaCharacteristic.visual) {
            item.tracks.first!.isEnabled = false
        }
    }
}

func applicationWillEnterForeground(_ application: UIApplication) {
    if let item = yourAVPlayer.currentItem {
        if item.tracks.first!.assetTrack.hasMediaCharacteristic(AVMediaCharacteristic.visual) {
            item.tracks.first!.isEnabled = true
        }
    }
}

Of course do not forget to turn on TARGETS -> Capabilities -> Background Modes -> Audio, AirPlay, and Picture in Picture and enter the required AVAudioSession in AppDelegate.

Upvotes: 0

Andrey M.
Andrey M.

Reputation: 3079

Use this workaround in your ViewController class:

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(willEnterForeground),
                                           name: NSNotification.Name.UIApplicationWillEnterForeground,
                                           object: nil)
}

func willEnterForeground() {
    player!.play()
}

Hope it helps to anyone. Good luck!

Upvotes: 0

Twitter khuong291
Twitter khuong291

Reputation: 11702

First of all you need to create a class called VideoCutter in a file VideoCutter.swift:

import UIKit
import AVFoundation

extension String {
  var convert: NSString { return (self as NSString) }
}

public class VideoCutter: NSObject {
  public func cropVideoWithUrl(videoUrl url: NSURL, startTime: CGFloat, duration: CGFloat, completion: ((videoPath: NSURL?, error: NSError?) -> Void)?) {
    let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
    dispatch_async(dispatch_get_global_queue(priority, 0)) {
      let asset = AVURLAsset(URL: url, options: nil)
      let exportSession = AVAssetExportSession(asset: asset, presetName: "AVAssetExportPresetHighestQuality")
      let paths: NSArray = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
      var outputURL = paths.objectAtIndex(0) as! String
      let manager = NSFileManager.defaultManager()
      do {
        try manager.createDirectoryAtPath(outputURL, withIntermediateDirectories: true, attributes: nil)
      } catch _ {
      }
      outputURL = outputURL.convert.stringByAppendingPathComponent("sunny.mp4")
      do {
        try manager.removeItemAtPath(outputURL)
      } catch _ {
      }
      if let exportSession = exportSession as AVAssetExportSession? {
        exportSession.outputURL = NSURL(fileURLWithPath: outputURL)
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.outputFileType = AVFileTypeMPEG4
        let start = CMTimeMakeWithSeconds(Float64(startTime), 600)
        let duration = CMTimeMakeWithSeconds(Float64(duration), 600)
        let range = CMTimeRangeMake(start, duration)
        exportSession.timeRange = range
        exportSession.exportAsynchronouslyWithCompletionHandler { () -> Void in
          switch exportSession.status {
          case AVAssetExportSessionStatus.Completed:
            completion?(videoPath: exportSession.outputURL, error: nil)
          case AVAssetExportSessionStatus.Failed:
            print("Failed: \(exportSession.error)")
          case AVAssetExportSessionStatus.Cancelled:
            print("Failed: \(exportSession.error)")
          default:
            print("default case")
          }
        }
      }
      dispatch_async(dispatch_get_main_queue()) {
      }
    }
  }
}

Then you need to create one class VideoSplashViewController in VideoSplashViewController.swift file:

import UIKit
import MediaPlayer
import AVKit

public enum ScalingMode {
  case Resize
  case ResizeAspect
  case ResizeAspectFill
}

public class VideoSplashViewController: UIViewController {

  private let moviePlayer = AVPlayerViewController()
  private var moviePlayerSoundLevel: Float = 1.0
  public var contentURL: NSURL = NSURL() {
    didSet {
      setMoviePlayer(contentURL)
    }
  }

  public var videoFrame: CGRect = CGRect()
  public var startTime: CGFloat = 0.0
  public var duration: CGFloat = 0.0
  public var backgroundColor: UIColor = UIColor.blackColor() {
    didSet {
      view.backgroundColor = backgroundColor
    }
  }
  public var sound: Bool = true {
    didSet {
      if sound {
        moviePlayerSoundLevel = 1.0
      }else{
        moviePlayerSoundLevel = 0.0
      }
    }
  }
  public var alpha: CGFloat = CGFloat() {
    didSet {
      moviePlayer.view.alpha = alpha
    }
  }
  public var alwaysRepeat: Bool = true {
    didSet {
      if alwaysRepeat {
        NSNotificationCenter.defaultCenter().addObserver(self,
          selector: "playerItemDidReachEnd",
          name: AVPlayerItemDidPlayToEndTimeNotification,
          object: moviePlayer.player?.currentItem)
      }
    }
  }
  public var fillMode: ScalingMode = .ResizeAspectFill {
    didSet {
      switch fillMode {
      case .Resize:
        moviePlayer.videoGravity = AVLayerVideoGravityResize
      case .ResizeAspect:
        moviePlayer.videoGravity = AVLayerVideoGravityResizeAspect
      case .ResizeAspectFill:
        moviePlayer.videoGravity = AVLayerVideoGravityResizeAspectFill
      }
    }
  }

  override public func viewDidAppear(animated: Bool) {
    moviePlayer.view.frame = videoFrame
    moviePlayer.showsPlaybackControls = false
    view.addSubview(moviePlayer.view)
    view.sendSubviewToBack(moviePlayer.view)
  }

  override public func viewWillDisappear(animated: Bool) {
    super.viewDidDisappear(animated)
    NSNotificationCenter.defaultCenter().removeObserver(self)
  }

  private func setMoviePlayer(url: NSURL){
    let videoCutter = VideoCutter()
    videoCutter.cropVideoWithUrl(videoUrl: url, startTime: startTime, duration: duration) { (videoPath, error) -> Void in
      if let path = videoPath as NSURL? {
        let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
        dispatch_async(dispatch_get_global_queue(priority, 0)) {
          dispatch_async(dispatch_get_main_queue()) {
            self.moviePlayer.player = AVPlayer(URL: path)
            self.moviePlayer.player?.play()
            self.moviePlayer.player?.volume = self.moviePlayerSoundLevel
          }
        }
      }
    }
  }

  override public func viewDidLoad() {
    super.viewDidLoad()
  }

  override public func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }

  func playerItemDidReachEnd() {
    moviePlayer.player?.seekToTime(kCMTimeZero)
    moviePlayer.player?.play()
  }
}

Finally, in your FirstViewController:

import UIKit

class FirstViewController: VideoSplashViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    setupVideoBackground()
}

func setupVideoBackground() {

    let url = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("sunny", ofType: "mp4")!)

    videoFrame = view.frame
    fillMode = .ResizeAspectFill
    alwaysRepeat = true
    sound = true
    startTime = 2.0
    alpha = 0.8

    contentURL = url
    view.userInteractionEnabled = false

    }
}

Upvotes: 1

Related Questions