Add twitch embed player wrapper

This commit is contained in:
Ilya Laktyushin 2019-11-13 20:24:17 +04:00
parent 401b8c2922
commit 8e60dc4da3
6 changed files with 149 additions and 22 deletions

View File

@ -25,12 +25,8 @@
}; };
})(); })();
function play() { function playPause() {
invoke("play"); invoke("playPause");
}
function pause() {
invoke("play");
} }
function receiveMessage(evt) { function receiveMessage(evt) {

View File

@ -1,5 +1,5 @@
function initialize() { function initialize() {
var controls = document.getElementsByClassName("player-controls-bottom")[0]; var controls = document.getElementsByClassName("pl-controls-bottom")[0];
if (controls == null) { if (controls == null) {
controls = document.getElementsByClassName("player-overlay-container")[0]; controls = document.getElementsByClassName("player-overlay-container")[0];
} }
@ -7,9 +7,14 @@ function initialize() {
controls.style.display = "none"; controls.style.display = "none";
} }
var root = document.getElementsByClassName("player-root")[0];
if (root != null) {
root.style.display = "none";
}
var topBar = document.getElementById("top-bar"); var topBar = document.getElementById("top-bar");
if (topBar == null) { if (topBar == null) {
topBar = document.getElementsByClassName("player-controls-top")[0]; topBar = document.getElementsByClassName("pl-controls-top")[0];
} }
if (topBar != null) { if (topBar != null) {
topBar.style.display = "none"; topBar.style.display = "none";
@ -17,7 +22,7 @@ function initialize() {
var pauseOverlay = document.getElementsByClassName("player-play-overlay")[0]; var pauseOverlay = document.getElementsByClassName("player-play-overlay")[0];
if (pauseOverlay == null) { if (pauseOverlay == null) {
pauseOverlay = document.getElementsByClassName("player-controls-bottom")[0]; pauseOverlay = document.getElementsByClassName("pl-controls-bottom")[0];
} }
if (pauseOverlay != null) { if (pauseOverlay != null) {
pauseOverlay.style.display = "none"; pauseOverlay.style.display = "none";
@ -85,13 +90,15 @@ function eventFire(el, etype){
} }
} }
function play() { function togglePlayPause() {
var playButton = document.getElementsByClassName("js-control-playpause-button")[0]; var playButton = document.getElementsByClassName("js-control-playpause-button")[0];
if (playButton == null) { if (playButton == null) {
playButton = document.getElementsByClassName("player-button--playpause")[0]; playButton = document.getElementsByClassName("player-button")[0];
} }
eventFire(playButton, "click"); if (playButton != null) {
eventFire(playButton, "click");
}
} }
function receiveMessage(evt) { function receiveMessage(evt) {
@ -105,8 +112,8 @@ function receiveMessage(evt) {
if (obj.command == "initialize") if (obj.command == "initialize")
initialize(); initialize();
else if (obj.command == "play") else if (obj.command == "playPause")
play(); togglePlayPause();
} catch (ex) { } } catch (ex) { }
} }

View File

@ -0,0 +1,117 @@
import Foundation
import WebKit
import SwiftSignalKit
import UniversalMediaPlayer
import AppBundle
func isTwitchVideoUrl(_ url: String) -> Bool {
return url.contains("//player.twitch.tv/") || url.contains("//clips.twitch.tv/")
}
final class TwitchEmbedImplementation: WebEmbedImplementation {
private var evalImpl: ((String) -> Void)?
private var updateStatus: ((MediaPlayerStatus) -> Void)?
private var onPlaybackStarted: (() -> Void)?
private let url: String
private var status : MediaPlayerStatus
private var started = false
init(url: String) {
self.url = url
self.status = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true), soundEnabled: true)
}
func setup(_ webView: WKWebView, userContentController: WKUserContentController, evaluateJavaScript: @escaping (String) -> Void, updateStatus: @escaping (MediaPlayerStatus) -> Void, onPlaybackStarted: @escaping () -> Void) {
let bundle = getAppBundle()
guard let userScriptPath = bundle.path(forResource: "TwitchUserScript", ofType: "js") else {
return
}
guard let userScriptData = try? Data(contentsOf: URL(fileURLWithPath: userScriptPath)) else {
return
}
guard let userScript = String(data: userScriptData, encoding: .utf8) else {
return
}
guard let htmlTemplatePath = bundle.path(forResource: "Twitch", ofType: "html") else {
return
}
guard let htmlTemplateData = try? Data(contentsOf: URL(fileURLWithPath: htmlTemplatePath)) else {
return
}
guard let htmlTemplate = String(data: htmlTemplateData, encoding: .utf8) else {
return
}
self.evalImpl = evaluateJavaScript
self.updateStatus = updateStatus
self.onPlaybackStarted = onPlaybackStarted
updateStatus(self.status)
let html = String(format: htmlTemplate, self.url)
webView.loadHTMLString(html, baseURL: URL(string: "about:blank"))
userContentController.addUserScript(WKUserScript(source: userScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
}
func play() {
if let eval = self.evalImpl {
eval("playPause()")
}
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: self.status.duration, dimensions: self.status.dimensions, timestamp: self.status.timestamp, baseRate: 1.0, seekId: self.status.seekId, status: .playing, soundEnabled: self.status.soundEnabled)
if let updateStatus = self.updateStatus {
updateStatus(self.status)
}
}
func pause() {
if let eval = self.evalImpl {
eval("playPause()")
}
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: self.status.duration, dimensions: self.status.dimensions, timestamp: self.status.timestamp, baseRate: 1.0, seekId: self.status.seekId, status: .paused, soundEnabled: self.status.soundEnabled)
if let updateStatus = self.updateStatus {
updateStatus(self.status)
}
}
func togglePlayPause() {
if self.status.status == .playing {
self.pause()
} else {
self.play()
}
}
func seek(timestamp: Double) {
}
func pageReady() {
// Queue.mainQueue().after(delay: 0.5) {
// if let onPlaybackStarted = self.onPlaybackStarted {
// onPlaybackStarted()
// }
// }
}
func callback(url: URL) {
switch url.host {
case "onPlayback":
if !self.started {
self.started = true
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: self.status.duration, dimensions: self.status.dimensions, timestamp: self.status.timestamp, baseRate: 1.0, seekId: self.status.seekId, status: .playing, soundEnabled: self.status.soundEnabled)
if let updateStatus = self.updateStatus {
updateStatus(self.status)
}
if let onPlaybackStarted = self.onPlaybackStarted {
onPlaybackStarted()
}
}
default:
break
}
}
}

View File

@ -134,7 +134,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation {
} }
func play() { func play() {
if let eval = evalImpl { if let eval = self.evalImpl {
eval("play();") eval("play();")
} }
@ -142,7 +142,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation {
} }
func pause() { func pause() {
if let eval = evalImpl { if let eval = self.evalImpl {
eval("pause();") eval("pause();")
} }
} }
@ -156,7 +156,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation {
} }
func seek(timestamp: Double) { func seek(timestamp: Double) {
if let eval = evalImpl { if let eval = self.evalImpl {
eval("seek(\(timestamp));") eval("seek(\(timestamp));")
} }
@ -165,7 +165,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation {
updateStatus(self.status) updateStatus(self.status)
} }
ignorePosition = 2 self.ignorePosition = 2
} }
func pageReady() { func pageReady() {

View File

@ -22,6 +22,7 @@ protocol WebEmbedImplementation {
public enum WebEmbedType { public enum WebEmbedType {
case youtube(videoId: String, timestamp: Int) case youtube(videoId: String, timestamp: Int)
case vimeo(videoId: String, timestamp: Int) case vimeo(videoId: String, timestamp: Int)
case twitch(url: String)
case iframe(url: String) case iframe(url: String)
public var supportsSeeking: Bool { public var supportsSeeking: Bool {
@ -37,6 +38,10 @@ public enum WebEmbedType {
public func webEmbedType(content: TelegramMediaWebpageLoadedContent, forcedTimestamp: Int? = nil) -> WebEmbedType { public func webEmbedType(content: TelegramMediaWebpageLoadedContent, forcedTimestamp: Int? = nil) -> WebEmbedType {
if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: content.url) { if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: content.url) {
return .youtube(videoId: videoId, timestamp: forcedTimestamp ?? timestamp) return .youtube(videoId: videoId, timestamp: forcedTimestamp ?? timestamp)
} else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: content.url) {
return .vimeo(videoId: videoId, timestamp: forcedTimestamp ?? timestamp)
} else if let embedUrl = content.embedUrl, isTwitchVideoUrl(embedUrl) {
return .twitch(url: embedUrl)
} else { } else {
return .iframe(url: content.embedUrl ?? content.url) return .iframe(url: content.embedUrl ?? content.url)
} }
@ -48,6 +53,8 @@ func webEmbedImplementation(for type: WebEmbedType) -> WebEmbedImplementation {
return YoutubeEmbedImplementation(videoId: videoId, timestamp: timestamp) return YoutubeEmbedImplementation(videoId: videoId, timestamp: timestamp)
case let .vimeo(videoId, timestamp): case let .vimeo(videoId, timestamp):
return VimeoEmbedImplementation(videoId: videoId, timestamp: timestamp) return VimeoEmbedImplementation(videoId: videoId, timestamp: timestamp)
case let .twitch(url):
return TwitchEmbedImplementation(url: url)
case let .iframe(url): case let .iframe(url):
return GenericEmbedImplementation(url: url) return GenericEmbedImplementation(url: url)
} }

View File

@ -93,8 +93,8 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
private var ignoreEarlierTimestamps = false private var ignoreEarlierTimestamps = false
private var status : MediaPlayerStatus private var status : MediaPlayerStatus
private var ready: Bool = false private var ready = false
private var started: Bool = false private var started = false
private var ignorePosition: Int? private var ignorePosition: Int?
private enum PlaybackDelay { private enum PlaybackDelay {
@ -176,7 +176,7 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
return return
} }
if let eval = evalImpl { if let eval = self.evalImpl {
eval("play();") eval("play();")
} }
@ -184,7 +184,7 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
} }
func pause() { func pause() {
if let eval = evalImpl { if let eval = self.evalImpl {
eval("pause();") eval("pause();")
} }
} }