How to embed YouTube videos into your iOS app using Swift

UPDATE [December 24rd, 2020]: Change the implementation of XCDYouTubeKit, and also added support for playing videos in background

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.

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 of 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 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 that 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

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'

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 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 and import AVKit

import UIKit import AVKit import XCDYouTubeKit 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 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 with 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 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 questionsplease feel free to leave a comment below

Subscribe
Notify of
guest
12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jay

Do you have a SwiftUI example for this? Also does it play in background mode with screen lock and also silent key off?

Jay

I don’t want to push it to the App Store, but just want a way to play the music on a local app pushed through Xcode. 🙂

Gina

Thank you for this tutorial. I’m having issues playing your YouTube video on iOS 13. I see the preview but it just spins and spins. It never plays. Can you help? this is the error I’m getting and have no idea how or where to add the entitlement or flags:
Note: I get the same message on iOS 14 but it still plays the video.
Any help would be greatly appreciated

YouTubeExample[2889:1467279] [assertion] Error acquiring assertion: <NSError: 0x6000018574b0; domain: RBSAssertionErrorDomain; code: 2; reason: "Required client entitlement is missing"> {
    userInfo = {
        RBSAssertionAttribute = <RBSLegacyAttribute: 0x7fe62e209cd0; requestedReason: MediaPlayback; reason: MediaPlayback; flags: PreventTaskSuspend | PreventTaskThrottleDown | WantsForegroundResourcePriority>;
    }
}

shre

can we autoplay YouTube video using any of the above methods?

shre

thanks that worked!
Can I do autoplay with youtube-ios-player-helper also?
I am not inclined towards XCDYouTubeKit since app can rejected because of it.

shre

awesome, thank you!