diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index f0cc0bc51d..38babbc8fe 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -54,6 +54,7 @@ struct InitialPlayerSettings: Codable { } let userValues: UserValues + let ui: UIValues let settings: PlayerSettings let platform: Platform let locale: Locale @@ -69,6 +70,13 @@ public struct UserValues: Codable { let askModeOverlayHidden: Bool } +public struct UIValues: Codable { + enum CodingKeys: String, CodingKey { + case allowFirstVideo + } + let allowFirstVideo: Bool +} + public enum DuckPlayerReferrer { case youtube, other } @@ -193,6 +201,12 @@ final class DuckPlayer: DuckPlayerProtocol { askModeOverlayHidden: settings.askModeOverlayHidden ) } + + private func encodeUIValues() -> UIValues { + UIValues( + allowFirstVideo: settings.allowFirstVideo + ) + } @MainActor private func encodedPlayerSettings(with webView: WKWebView?) async -> InitialPlayerSettings { @@ -203,7 +217,12 @@ final class DuckPlayer: DuckPlayerProtocol { let locale = InitialPlayerSettings.Locale.en let playerSettings = InitialPlayerSettings.PlayerSettings(pip: pip) let userValues = encodeUserValues() - return InitialPlayerSettings(userValues: userValues, settings: playerSettings, platform: platform, locale: locale) + let uiValues = encodeUIValues() + return InitialPlayerSettings(userValues: userValues, + ui: uiValues, + settings: playerSettings, + platform: platform, + locale: locale) } // Accessing WKMessage needs main thread diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 86e3109d35..fbb6c0ae9f 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -108,6 +108,9 @@ final class DuckPlayerNavigationHandler { return } + // This is passed to the FE overlay at init to disable the overlay for one video + duckPlayer.settings.allowFirstVideo = false + if let (videoID, _) = url.youtubeVideoParams, videoID == lastHandledVideoID { os_log("DP: URL (%s) already handled, skipping", log: .duckPlayerLog, type: .debug, url.absoluteString) @@ -118,7 +121,8 @@ final class DuckPlayerNavigationHandler { // These should not be handled by DuckPlayer if url.isYoutubeVideo, url.hasWatchInYoutubeQueryParameter { - return + duckPlayer.settings.allowFirstVideo = true + return } if url.isYoutubeVideo, @@ -143,6 +147,9 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { os_log("DP: Handling DuckPlayer Player Navigation for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") + // This is passed to the FE overlay at init to disable the overlay for one video + duckPlayer.settings.allowFirstVideo = false + guard let url = navigationAction.request.url else { return } guard featureFlagger.isFeatureOn(.duckPlayer) else { @@ -167,6 +174,8 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { if let videoParameterItem = queryItems.first(where: { $0.name == Constants.watchInYoutubeVideoParameter }), let id = videoParameterItem.value, let newURL = URL.youtube(id, timestamp: nil).addingWatchInYoutubeQueryParameter() { + // These links should always skip the overlay + duckPlayer.settings.allowFirstVideo = true Pixel.fire(pixel: Pixel.Event.duckPlayerWatchOnYoutube) webView.load(URLRequest(url: newURL)) return @@ -220,6 +229,9 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { return } + // This is passed to the FE overlay at init to disable the overlay for one video + duckPlayer.settings.allowFirstVideo = false + if let (videoID, _) = url.youtubeVideoParams, videoID == lastHandledVideoID, !url.hasWatchInYoutubeQueryParameter { @@ -229,9 +241,10 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { } // Handle Youtube internal links like "Age restricted" and "Copyright restricted" videos - // These should not be handled by DuckPlayer + // These should not be handled by DuckPlayer and not include overlays if url.isYoutubeVideo, url.hasWatchInYoutubeQueryParameter { + duckPlayer.settings.allowFirstVideo = true completion(.allow) return } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift b/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift index 87a8af25f1..2f0ac79ea0 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift @@ -69,6 +69,7 @@ protocol DuckPlayerSettingsProtocol: AnyObject { var duckPlayerSettingsPublisher: AnyPublisher { get } var mode: DuckPlayerMode { get } var askModeOverlayHidden: Bool { get } + var allowFirstVideo: Bool { get set } init(appSettings: AppSettings, privacyConfigManager: PrivacyConfigurationManaging) @@ -133,6 +134,8 @@ final class DuckPlayerSettings: DuckPlayerSettingsProtocol { } } + var allowFirstVideo: Bool = false + private func registerConfigPublisher() { isFeatureEnabledCancellable = privacyConfigManager.updatesPublisher .map { [weak privacyConfigManager] in diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index 79e2cb7ef7..35fef664e7 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -87,6 +87,7 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { weak var webView: WKWebView? let messageOriginPolicy: MessageOriginPolicy = .only(rules: [ + .exact(hostname: "sosbourne.duckduckgo.com"), .exact(hostname: DuckPlayerSettings.OriginDomains.duckduckgo), .exact(hostname: DuckPlayerSettings.OriginDomains.youtube), .exact(hostname: DuckPlayerSettings.OriginDomains.youtubeMobile) diff --git a/DuckDuckGoTests/DuckPlayerMocks.swift b/DuckDuckGoTests/DuckPlayerMocks.swift index 38693256d5..38da508d06 100644 --- a/DuckDuckGoTests/DuckPlayerMocks.swift +++ b/DuckDuckGoTests/DuckPlayerMocks.swift @@ -95,6 +95,7 @@ final class MockDuckPlayerSettings: DuckPlayerSettingsProtocol { var mode: DuckPlayerMode = .disabled var askModeOverlayHidden: Bool = false + var allowFirstVideo: Bool = false init(appSettings: AppSettings = AppSettingsMock(), privacyConfigManager: any BrowserServicesKit.PrivacyConfigurationManaging) {} func triggerNotification() {}