Skip to content

Commit

Permalink
Created queue system to hold pending commands until YT is ready
Browse files Browse the repository at this point in the history
  • Loading branch information
qalandarov committed Feb 26, 2023
1 parent 4bc2c56 commit b635e5e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 24 deletions.
71 changes: 58 additions & 13 deletions YouTubePlayer/YouTubePlayer/YouTubePlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Copyright (c) 2014 Giles Van Gruisen. All rights reserved.
//

import Combine
import UIKit
import WebKit

Expand All @@ -14,6 +15,7 @@ public class YouTubePlayerView: UIView {

public enum State {
case idle
case loading
case ready(PlayerState)
case error(PlayerError)

Expand All @@ -23,11 +25,29 @@ public class YouTubePlayerView: UIView {
}
return false
}

fileprivate var isReady: Bool {
if case .ready = self {
return true
}
return false
}

fileprivate var isLoading: Bool {
if case .loading = self {
return true
}
return false
}
}

@Published private(set) public var state: State = .idle
@Published private(set) public var quality: PlaybackQuality = .auto

private var stateWatcherCancellable: AnyCancellable?

private var queue: [(String, ((Any?) -> Void)?)] = []

/// A Boolean value that indicates whether HTML5 videos play inline or use the native full-screen controller.
public var allowsInlineMediaPlayback = false

Expand Down Expand Up @@ -140,19 +160,24 @@ public extension YouTubePlayerView {

private extension YouTubePlayerView {
func setupPlayer(with parameters: PlayerParameters) {
state = .loading

guard let path = Bundle(for: YouTubePlayerView.self).path(forResource: "YTPlayer", ofType: "html") else {
printLog("Unable to get the path of the YTPlayer.html")
state = .error(.unexpected)
return
}

guard let rawHTMLString = try? String(contentsOfFile: path) else {
printLog("Unable to get HTML from player file in bundle")
state = .error(.unexpected)
return
}

guard let encoded = try? JSONEncoder().encode(parameters),
let jsonParameters = String(data: encoded, encoding: .utf8) else {
printLog("Unable to get JSON serialized parameters string")
state = .error(.invalidParameter)
return
}

Expand All @@ -163,19 +188,6 @@ private extension YouTubePlayerView {
webView.loadHTMLString(htmlString, baseURL: URL(string: "about:blank"))
}

func evaluatePlayerCommand(_ command: String, completion: ((Any?) -> Void)? = nil) {
let fullCommand = "player." + command + ";"
webView.evaluateJavaScript(fullCommand) { (result, error) in
if let error = error, (error as NSError).code != 5 { // NOTE: ignore :Void return
print(error)
printLog("Error executing javascript")
completion?(nil)
}

completion?(result)
}
}

func handleJSEvent(_ eventURL: URL) {
// Data is not always used,q that's why not guarding
let data = eventURL.queryValue(for: "data")
Expand All @@ -201,6 +213,39 @@ private extension YouTubePlayerView {
}
}
}

func evaluatePlayerCommand(_ command: String, completion: ((Any?) -> Void)? = nil) {
guard state.isLoading else {
evaluate(command, completion: completion)
return
}

// Queue up commands if YT is still loading and process them once YT is ready
queue.append((command, completion))

if stateWatcherCancellable == nil {
stateWatcherCancellable = $state.sink { [weak self] state in
guard let self, state.isReady else { return }
for (command, completion) in self.queue {
self.evaluate(command, completion: completion)
}
self.queue.removeAll()
self.stateWatcherCancellable = nil
}
}
}

func evaluate(_ command: String, completion: ((Any?) -> Void)? = nil) {
let fullCommand = "player." + command + ";"
webView.evaluateJavaScript(fullCommand) { (result, error) in
if let error = error, (error as NSError).code != 5 { // NOTE: ignore :Void return
printLog("Error executing javascript: \(error.localizedDescription)")
completion?(nil)
}

completion?(result)
}
}
}


Expand Down
38 changes: 27 additions & 11 deletions YouTubePlayerExample/YouTubePlayerExample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,43 @@ class ViewController: UIViewController {
loadVideo()
}

private func loadVideo() {
@IBAction func loadPlaylist(sender: UIButton) {
playerView.loadPlaylistID("PLOU2XLYxmsILw2VmhxUWyd_imCrJef_iz")
playerView.playVideo()
getDuration()
}

@IBAction func currentTime(sender: UIButton) {
getCurrentTime()
}

@IBAction func duration(sender: UIButton) {
getDuration()
}
}

// MARK: - Player helpers

private extension ViewController {
func loadVideo() {
playerView.playerParams.playsInline = true
playerView.playerParams.showControls = false
playerView.playerParams.startAt = 180
playerView.playerParams.showRecommendedFromOtherChannels = true
playerView.loadVideoID("UF8uR6Z6KLc")
playerView.playVideo()
getDuration()
}

@IBAction func loadPlaylist(sender: UIButton) {
playerView.loadPlaylistID("PLOU2XLYxmsILw2VmhxUWyd_imCrJef_iz")
}

@IBAction func currentTime(sender: UIButton) {
playerView.getCurrentTime(completion: { (time) in
func getCurrentTime() {
playerView.getCurrentTime { time in
let title = String(format: "Current Time %.2f", time ?? 0)
self.currentTimeButton.setTitle(title, for: .normal)
})
}
}
@IBAction func duration(sender: UIButton) {
playerView.getDuration { (duration) in

func getDuration() {
playerView.getDuration { duration in
let title = String(format: "Duration %.2f", duration ?? 0)
self.durationButton.setTitle(title, for: .normal)
}
Expand Down

0 comments on commit b635e5e

Please sign in to comment.