diff --git a/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/Twitch.html b/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/Twitch.html index b1b5faa9e3..53fb685257 100755 --- a/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/Twitch.html +++ b/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/Twitch.html @@ -25,12 +25,8 @@ }; })(); - function play() { - invoke("play"); - } - - function pause() { - invoke("play"); + function playPause() { + invoke("playPause"); } function receiveMessage(evt) { diff --git a/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/TwitchUserScript.js b/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/TwitchUserScript.js index c4865582a7..552a6e9aad 100644 --- a/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/TwitchUserScript.js +++ b/submodules/TelegramUI/TelegramUI/Resources/WebEmbed/TwitchUserScript.js @@ -1,5 +1,5 @@ function initialize() { - var controls = document.getElementsByClassName("player-controls-bottom")[0]; + var controls = document.getElementsByClassName("pl-controls-bottom")[0]; if (controls == null) { controls = document.getElementsByClassName("player-overlay-container")[0]; } @@ -7,9 +7,14 @@ function initialize() { controls.style.display = "none"; } + var root = document.getElementsByClassName("player-root")[0]; + if (root != null) { + root.style.display = "none"; + } + var topBar = document.getElementById("top-bar"); if (topBar == null) { - topBar = document.getElementsByClassName("player-controls-top")[0]; + topBar = document.getElementsByClassName("pl-controls-top")[0]; } if (topBar != null) { topBar.style.display = "none"; @@ -17,7 +22,7 @@ function initialize() { var pauseOverlay = document.getElementsByClassName("player-play-overlay")[0]; if (pauseOverlay == null) { - pauseOverlay = document.getElementsByClassName("player-controls-bottom")[0]; + pauseOverlay = document.getElementsByClassName("pl-controls-bottom")[0]; } if (pauseOverlay != null) { 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]; 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) { @@ -105,8 +112,8 @@ function receiveMessage(evt) { if (obj.command == "initialize") initialize(); - else if (obj.command == "play") - play(); + else if (obj.command == "playPause") + togglePlayPause(); } catch (ex) { } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/TwitchEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/TwitchEmbedImplementation.swift new file mode 100644 index 0000000000..ea36275ed9 --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/Sources/TwitchEmbedImplementation.swift @@ -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 + } + } +} diff --git a/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift index 7b9621ddb0..d0b94965aa 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift @@ -134,7 +134,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation { } func play() { - if let eval = evalImpl { + if let eval = self.evalImpl { eval("play();") } @@ -142,7 +142,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation { } func pause() { - if let eval = evalImpl { + if let eval = self.evalImpl { eval("pause();") } } @@ -156,7 +156,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation { } func seek(timestamp: Double) { - if let eval = evalImpl { + if let eval = self.evalImpl { eval("seek(\(timestamp));") } @@ -165,7 +165,7 @@ final class VimeoEmbedImplementation: WebEmbedImplementation { updateStatus(self.status) } - ignorePosition = 2 + self.ignorePosition = 2 } func pageReady() { diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift index 5ebd43ed5d..12db0f5a2f 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift @@ -22,6 +22,7 @@ protocol WebEmbedImplementation { public enum WebEmbedType { case youtube(videoId: String, timestamp: Int) case vimeo(videoId: String, timestamp: Int) + case twitch(url: String) case iframe(url: String) public var supportsSeeking: Bool { @@ -37,6 +38,10 @@ public enum WebEmbedType { public func webEmbedType(content: TelegramMediaWebpageLoadedContent, forcedTimestamp: Int? = nil) -> WebEmbedType { if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: content.url) { 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 { return .iframe(url: content.embedUrl ?? content.url) } @@ -48,6 +53,8 @@ func webEmbedImplementation(for type: WebEmbedType) -> WebEmbedImplementation { return YoutubeEmbedImplementation(videoId: videoId, timestamp: timestamp) case let .vimeo(videoId, timestamp): return VimeoEmbedImplementation(videoId: videoId, timestamp: timestamp) + case let .twitch(url): + return TwitchEmbedImplementation(url: url) case let .iframe(url): return GenericEmbedImplementation(url: url) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift index 421bac9852..7bc1d1bf88 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift @@ -93,8 +93,8 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation { private var ignoreEarlierTimestamps = false private var status : MediaPlayerStatus - private var ready: Bool = false - private var started: Bool = false + private var ready = false + private var started = false private var ignorePosition: Int? private enum PlaybackDelay { @@ -176,7 +176,7 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation { return } - if let eval = evalImpl { + if let eval = self.evalImpl { eval("play();") } @@ -184,7 +184,7 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation { } func pause() { - if let eval = evalImpl { + if let eval = self.evalImpl { eval("pause();") } }