Last updated on: May 27, 2023
In this tutorial, I’m going to show you how to embed a youtube video in your iOS app with three different methods, using the official library (YouTube Player iOS Helper), a 3rd party library called XCDYouTubeKit, and WKWebView.
Contents
1st Method (YouTube Player iOS Helper)
Pros
- Official library provided by YouTube
Cons
- Doesn’t get updated very often
- Cannot play private videos, but it can play unlisted videos
- Nearly identical to a player embedded on a webpage in a mobile browser
YouTube Player iOS Helper is the official way to embed a youtube video in your iOS app. It’s a recommended method if you don’t want to display a lot of videos in your app.
Adding the library
In this example, we are going to add the YouTube Player iOS Helper library into our project using Cocaopods.
Open your Podfile with your favorite text editor (e.g Visual Studio Code) and add pod 'youtube-ios-player-helper'
Then, run pod update
in the terminal
Add an UIView in your ViewController and under the Custom Class, in the Class field, add YTPlayerView
If you want to have a 16:9 aspect ratio in your YTPlayerView, set the top, leading, and trailing equals to Superview (or Safe Area) of your ViewController
Make the width of the YTPlayerView equal to the width of the ViewController
Add Aspect Ratio to the YTPlayerView
…and set the Multiplier to 16:9
Next, in the swift file of your ViewController, add with drag and drop the YTPlayerView and add import youtube_ios_player_helper
at the top of your file.
Now, add a video to the player using the id of the video:
override func viewDidLoad() {
super.viewDidLoad()
playerView.load(withVideoId: "YE7VzlLtp-4", playerVars: ["playsinline": "1"])
}
Code language: Swift (swift)
You can set different variables to the player, like playsinline which allows you to play the video inside the ViewController instead of full-screen mode when you start the video.
If you want more customization, like background color or custom loading view, add the YTPlayerViewDelegate to your class:
class YouTubePlayeriOSHelperViewController: UIViewController {
@IBOutlet var playerView: YTPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
playerView.delegate = self
// ...
}
}
extension YouTubePlayeriOSHelperViewController: YTPlayerViewDelegate {
func playerViewPreferredWebViewBackgroundColor(_ playerView: YTPlayerView) -> UIColor {
return UIColor.black
}
func playerViewPreferredInitialLoading(_ playerView: YTPlayerView) -> UIView? {
// let customLoadingView = UIView()
// Create a custom loading view
// return customLoadingView
}
}
Code language: Swift (swift)
2nd Method (3rd Party Library XCDYouTubeKit)
Pros
- Better performance
Cons
- You may get rejected from App Store (Which never happened to me)
- Some videos don’t show a thumbnail
In this method, we’re going to use the 3rd party library XCDYouTubeKit, made by 0xced, to embed our YouTube video into our app.
Adding the XCDYouTubeKit library
In this example, we are going to add the XCDYouTubeKit library into our project using Cocaopods.
Open your Podfile with your favorite text editor (e.g. Visual Studio Code) and add pod 'XCDYouTubeKit-kbexdev'
Because the official
XCDYouTubeKit
library stopped working, we’re using kbexdev’s with the fix.
Then, run pod update
in the terminal
Add an UIView in your ViewController
If you want to have a 16:9 aspect ratio in your UIView, set the top, leading, and trailing equals to Superview (or Safe Area) of your ViewController
Make the width of the UIView equal to the width of the ViewController
Add Aspect Ratio to the UIView
…and set the Multiplier to 16:9
Next, in the swift file of your ViewController, add with drag and drop the UIView and give it the name playerViewContainer.
Create a new swift file, name it AVPlayerViewControllerManager and paste the following code:
//
//Â AVPlayerViewControllerManager.swift
//Â XCDYouTubeKit iOS Demo
//
// Created by Soneé John on 10/29/19.
// Copyright © 2019 Cédric Luthi. All rights reserved.
//
import AVKit
import Foundation
import MediaPlayer
import XCDYouTubeKit_kbexdev
extension UIViewController {
  func topMostViewController() -> UIViewController {
    if self.presentedViewController == nil {
      return self
    }
    if let navigation = self.presentedViewController as? UINavigationController {
      return navigation.visibleViewController!.topMostViewController()
    }
    if let tab = self.presentedViewController as? UITabBarController {
      if let selectedTab = tab.selectedViewController {
        return selectedTab.topMostViewController()
      }
      return tab.topMostViewController()
    }
    return self.presentedViewController!.topMostViewController()
  }
}
extension UIView {
  var parentViewController: UIViewController? {
    var parentResponder: UIResponder? = self
    while parentResponder != nil {
      parentResponder = parentResponder!.next
      if let viewController = parentResponder as? UIViewController {
        return viewController
      }
    }
    return nil
  }
}
@objcMembers class AVPlayerViewControllerManager: NSObject {
  // MARK: - Public
  public static let shared = AVPlayerViewControllerManager()
  public var lowQualityMode = false
  public dynamic var duration: Float = 0
  public var video: XCDYouTubeVideo? {
    didSet {
      guard let video = video else { return }
      guard self.lowQualityMode == false else {
        guard let streamURL = video.streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] ?? video.streamURLs[XCDYouTubeVideoQuality.medium360.rawValue] ?? video.streamURLs[XCDYouTubeVideoQuality.small240.rawValue] else { fatalError("No stream URL") }
        self.player = AVPlayer(url: streamURL)
        self.controller.player = self.player
        return
      }
      let streamURL = video.streamURL
      self.player = AVPlayer(url: streamURL!)
      self.controller.player = self.player
    }
  }
  public var player: AVPlayer? {
    didSet {
      if let playerRateObserverToken = playerRateObserverToken {
        playerRateObserverToken.invalidate()
        self.playerRateObserverToken = nil
      }
      self.playerRateObserverToken = self.player?.observe(\.rate, changeHandler: { _, _ in
        self.updatePlaybackRateMetadata()
      })
      Â
      guard let video = self.video else { return }
      if let token = timeObserverToken {
        oldValue?.removeTimeObserver(token)
        self.timeObserverToken = nil
      }
      self.setupRemoteTransportControls()
      self.updateGeneralMetadata(video: video)
      self.updatePlaybackDuration()
    }
  }
  public lazy var controller: AVPlayerViewController = {
    let controller = AVPlayerViewController()
    if #available(iOS 10.0, *) {
      controller.updatesNowPlayingInfoCenter = false
    }
    return controller
  }()
  override init() {
    super.init()
    NotificationCenter.default.addObserver(forName: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance(), queue: .main) { notification in
      guard let userInfo = notification.userInfo,
         let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
         let type = AVAudioSession.InterruptionType(rawValue: typeValue)
      else {
        return
      }
      if type == .began {
        self.player?.pause()
      } else if type == .ended {
        guard (try? AVAudioSession.sharedInstance().setActive(true)) != nil else { return }
        guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
        let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
        guard options.contains(.shouldResume) else { return }
        self.player?.play()
      }
    }
  }
  public func disconnectPlayer() {
    self.controller.player = nil
  }
  public func reconnectPlayer(rootViewController: UIViewController) {
    let viewController = rootViewController.topMostViewController()
    guard let playerViewController = viewController as? AVPlayerViewController else {
      if rootViewController is UINavigationController {
        guard let vc = (rootViewController as! UINavigationController).visibleViewController else { return }
        for childVC in vc.children {
          guard let playerViewController = childVC as? AVPlayerViewController else { continue }
          playerViewController.player = self.player
          break
        }
      }
      return
    }
    playerViewController.player = self.player
  }
  // MARK: Private
  fileprivate var playerRateObserverToken: NSKeyValueObservation?
  fileprivate var timeObserverToken: Any?
  fileprivate let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
  Â
  fileprivate func setupRemoteTransportControls() {
    let commandCenter = MPRemoteCommandCenter.shared()
    commandCenter.playCommand.addTarget { [unowned self] _ in
      if self.player?.rate == 0.0 {
        self.player?.play()
        return .success
      }
      return .commandFailed
    }
    commandCenter.pauseCommand.addTarget { _ in
      if self.player?.rate == 1.0 {
        self.player?.pause()
        return .success
      }
      return .commandFailed
    }
  }
  Â
  fileprivate func updateGeneralMetadata(video: XCDYouTubeVideo) {
    guard self.player?.currentItem != nil else {
      self.nowPlayingInfoCenter.nowPlayingInfo = nil
      return
    }
    Â
    var nowPlayingInfo = self.nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
    let title = video.title
    if let thumbnailURL = video.thumbnailURLs {
      URLSession.shared.dataTask(with: thumbnailURL[0]) { data, _, error in
        guard error == nil else { return }
        guard data != nil else { return }
        guard let image = UIImage(data: data!) else { return }
        let artwork = MPMediaItemArtwork(image: image)
        nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
        self.nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
      }.resume()
    }
    Â
    nowPlayingInfo[MPMediaItemPropertyTitle] = title
    self.nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
  }
  fileprivate func updatePlaybackDuration() {
    let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
    timeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] _ in
      guard let player = self?.player else { return }
      guard player.currentItem != nil else { return }
      var nowPlayingInfo = self!.nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
      self!.duration = Float(CMTimeGetSeconds(player.currentItem!.duration))
      nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = self!.duration
      nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentItem!.currentTime())
      self!.nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
    })
  }
  fileprivate func updatePlaybackRateMetadata() {
    guard self.player?.currentItem != nil else {
      self.duration = 0
      self.nowPlayingInfoCenter.nowPlayingInfo = nil
      return
    }
    Â
    var nowPlayingInfo = self.nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
    nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = self.player!.rate
    nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = self.player!.rate
  }
}
Code language: Swift (swift)
This file contains all the classes, methods, and extensions we need to set up the player easier (also includes the code for playing video in the background, which I’m going to show you later).
Now, go back to the previous file and initialize the AVPlayerViewController.
Also, at the top of your file, add import XCDYouTubeKit_kbexdev
and import AVKit
import UIKit
import AVKit
import XCDYouTubeKit_kbexdev
class XCDYouTubeKitViewController: UIViewController {
@IBOutlet var playerViewContainer: UIView!
let playerViewController = AVPlayerViewController()
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
}
Code language: Swift (swift)
Next, set up the player and make sure to pause it when you navigate to the previous ViewController
import AVKit
import UIKit
import XCDYouTubeKit_kbexdev
class XCDYouTubeKitViewController: UIViewController {
  @IBOutlet var playerViewContainer: UIView!
  Â
  let playerViewController = AVPlayerViewController()
  Â
  override func viewDidLoad() {
    super.viewDidLoad()
    Â
    let videoId = "YE7VzlLtp-4"
    Â
    XCDYouTubeClient.default().getVideoWithIdentifier(videoId, completionHandler: { [self] video, error in
      if let video = video {
        AVPlayerViewControllerManager.shared.video = video
        let playerViewController = AVPlayerViewControllerManager.shared.controller
        playerViewController.allowsPictureInPicturePlayback = false
        playerViewController.view.frame = playerViewContainer.bounds
        addChild(playerViewController)
        if let view = playerViewController.view {
          playerViewContainer.addSubview(view)
        }
        playerViewController.didMove(toParent: self)
      } else {
        print(error!)
      }
    })
  }
  Â
  // Stop playing the video when you go back
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    AVPlayerViewControllerManager.shared.controller.player?.pause()
  }
}
Code language: Swift (swift)
Playing video in the background
With XCDYouTubeKit you can play videos in the background, even with the screen turned off.
Keep in mind that Apple may reject your app for that.
Go to your project name > select the Signing & Capabilities tab > press the + Capability button
Double-click the Background Modes capability
And check the box Audio, AirPlay, and Picture in Picture
Now, go to your AppDelegate.swift file and enable the playback for the AVPlayer in the didFinishLaunchingWithOptions
import UIKit
import AVKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    Â
    do {
      try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
      try AVAudioSession.sharedInstance().setActive(true)
    } catch let error as NSError {
      print(error.localizedDescription)
    }
    Â
    Â
    return true
  }
  // ...
}
Code language: Swift (swift)
And on the same file, remove the player when your app goes in the background and reconnect it when it comes back.
// ...
func applicationDidEnterBackground(_ application: UIApplication) {
  AVPlayerViewControllerManager.shared.disconnectPlayer()
}
func applicationWillEnterForeground(_ application: UIApplication) {
  AVPlayerViewControllerManager.shared.reconnectPlayer(rootViewController: (window?.rootViewController)!)
}
// ...
Code language: Swift (swift)
3rd Method (WKWebView)
Pros
- You don’t need any library
- It’s almost the same as the official library
Cons
- Cannot play private videos, but it can play unlisted videos
- You have to create the WKWebView programmatically to work properly
Adding YouTube Player with WKWebView into our app
Add an UIView in your ViewController
If you want to have a 16:9 aspect ratio in your UIView, set the top, leading, and trailing equals to Superview (or Safe Area) of your ViewController
Make the width of the UIView equal to the width of the ViewController
Add Aspect Ratio to the UIView
…and set the Multiplier to 16:9
Next, in the swift file of your ViewController, add with drag and drop the UIView and give it the name webPlayerView and add import WebKit
at the top of your file.
Then, select your project in the Project Navigator, choose your app in Targets, select Build Phases, and under the Link Binary With Libraries, press the + button, and add the WebKit.framework into your project.
Now, go back to the swift file, declare a WKWebView, create a WKWebViewConfiguration, and use the allowsInlineMediaPlayback to play the video inside the ViewController instead of full-screen mode when you start the video.
Next, create a WKWebView giving the frame of the UIView we created before, and make a URLRequest
import UIKit
import WebKit
class WebView: UIViewController {
@IBOutlet var webPlayerView: UIView!
var webPlayer: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let webConfiguration = WKWebViewConfiguration()
webConfiguration.allowsInlineMediaPlayback = true
DispatchQueue.main.async {
self.webPlayer = WKWebView(frame: self.webPlayerView.bounds, configuration: webConfiguration)
self.webPlayerView.addSubview(self.webPlayer)
guard let videoURL = URL(string: "https://www.youtube.com/embed/YE7VzlLtp-4?playsinline=1") else { return }
let request = URLRequest(url: videoURL)
self.webPlayer.load(request)
}
}
}
Code language: Swift (swift)
Don’t forget to add the ?playsinline=1
parameter at the end of your embedded video link, so the allowsInlineMediaPlayback to work correctly.
You can find the final project here
If you have any questions, please feel free to leave a comment below