diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 93a652b7e..48a3deb0f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,2 @@ -- Windows support for Malicious website protection (#1423) -- history: adding item selection (#1486) -- Duck Player screenshot update (#1489) \ No newline at end of file +- Duck Player Custom Error Translations (#1500) +- Custom error messages for Duck Player (#1421) \ No newline at end of file diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.css b/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.css index cff98b790..7e8fbf3a0 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.css +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.css @@ -61,6 +61,7 @@ body[data-display=app] { /* pages/duckplayer/app/components/Components.module.css */ .Components_main { + background-color: #000; color: white; max-width: 3840px; margin: 0 auto; @@ -112,7 +113,7 @@ body[data-display=app] { text-decoration: none; } [data-layout=mobile] .Button_button { - background-color: #2f2f2f; + background-color: rgba(255, 255, 255, 0.12); } .Button_button:hover, .Button_button:focus-visible { @@ -188,7 +189,7 @@ body[data-display=app] { .SwitchBarMobile_switchBar { display: grid; border-radius: 8px; - background: #2f2f2f; + background: rgba(255, 255, 255, 0.12); padding-inline: 16px; height: 100%; line-height: 1.1; @@ -518,6 +519,9 @@ body[data-display=app] { .Wordmark_mobile_logo { height: 100px; } + [data-youtube-error=true] .Wordmark_mobile_logo { + height: 44px; + } } .Wordmark_mobile_logoSvg img { display: block; @@ -589,6 +593,127 @@ body[data-display=app] { } } +/* pages/duckplayer/app/components/YouTubeError.module.css */ +.YouTubeError_error { + align-items: center; + background: rgba(0, 0, 0, 0.6); + display: grid; + height: 100%; + justify-items: center; +} +.YouTubeError_error.YouTubeError_desktop { + height: var(--frame-height); + overflow: hidden; + position: relative; + z-index: 1; +} +.YouTubeError_error.YouTubeError_mobile { + border-radius: var(--inner-radius); + height: 100%; + overflow: auto; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_error.YouTubeError_mobile { + aspect-ratio: 16 / 9; + } +} +.YouTubeError_desktop { + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.YouTubeError_container { + column-gap: 24px; + display: flex; + flex-flow: row; + margin: 0; + max-width: 680px; + padding: 0 40px; + row-gap: 4px; +} +.YouTubeError_mobile .YouTubeError_container { + flex-flow: column; + padding: 0 24px; +} +@media screen and (min-height: 320px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 16px 0; + } +} +@media screen and (min-width: 375px) and (min-height: 400px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 36px 0; + } +} +.YouTubeError_content { + display: flex; + flex-direction: column; + gap: 4px; + margin: 16px 0; +} +@media screen and (min-width: 600px) { + .YouTubeError_content { + margin: 24px 0; + } +} +.YouTubeError_icon { + align-self: center; + display: flex; + justify-content: center; +} +.YouTubeError_icon::before { + content: " "; + display: block; + background: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A%0A') no-repeat; + height: 48px; + width: 48px; +} +@media screen and (max-width: 320px) { + .YouTubeError_icon { + display: none; + } +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_icon { + justify-content: start; + } + .YouTubeError_icon::before { + background-image: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A %0A%0A'); + height: 96px; + width: 128px; + } +} +.YouTubeError_heading { + color: #fff; + font-size: 20px; + font-weight: 700; + line-height: calc(24 / 20); + margin: 0; +} +.YouTubeError_messages { + color: #ccc; + font-size: 16px; + line-height: calc(24 / 16); +} +div.YouTubeError_messages { + display: flex; + flex-direction: column; + gap: 24px; +} +div.YouTubeError_messages p { + margin: 0; +} +p.YouTubeError_messages { + margin: 0; +} +ul.YouTubeError_messages li { + list-style: disc; + margin-left: 24px; +} + /* pages/duckplayer/app/components/MobileApp.module.css */ body[data-display=app] { padding: 8px; @@ -626,6 +751,7 @@ html[data-focus-mode=on] .MobileApp_hideInFocus { --inner-radius: 12px; --logo-width: 157px; --inner-padding: 8px; + --mobile-buttons-padding: 8px; position: relative; max-width: 100vh; margin: 0 auto; @@ -681,6 +807,15 @@ html[data-focus-mode=on] .MobileApp_embed { grid-area: switch; height: 44px; } +.MobileApp_detachedControls { + grid-area: detached; + display: flex; + flex-flow: column; + gap: 8px; + padding: 8px; + background: #2f2f2f; + border-radius: 12px; +} @media screen and (min-width: 425px) and (max-height: 600px) { .MobileApp_main { grid-template-rows: max-content auto max-content max-content 12px max-content auto; @@ -774,6 +909,71 @@ html[data-focus-mode=on] .MobileApp_embed { justify-content: end; } } +@media screen and (max-width: 599px) { + .MobileApp_main[data-youtube-error=true] { + --bg-color: transparent; + --inner-padding: 4px; + grid-template-areas: "logo" "gap3" "embed" "gap4" "switch" "buttons"; + grid-template-rows: max-content 16px auto 12px max-content max-content; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + background: #2f2f2f; + border-radius: var(--outer-radius); + padding: 4px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_switch { + background: #2f2f2f; + padding: 8px 8px 0 8px; + height: 60px; + max-height: 60px; + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + background: #2f2f2f; + padding: 8px; + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_buttons { + border-radius: var(--outer-radius); + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_switch { + background: transparent; + max-height: 0; + } +} +@media screen and (max-width: 599px) and (max-height: 599px) { + .MobileApp_main[data-youtube-error=true] { + max-width: unset; + grid-template-rows: 0 0 auto 12px 0 max-content; + } + .MobileApp_main[data-youtube-error=true] :is(.MobileApp_logo, .MobileApp_switch) { + display: none; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + } +} +@media screen and (min-width: 600px) and (max-height: 450px) { + .MobileApp_main[data-youtube-error=true] { + grid-template-areas: "embed" "buttons" "gap5"; + grid-template-rows: auto max-content 8px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + display: block; + } +} +@media screen and (max-height: 320px) { + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + overflow-y: auto; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + bottom: 0; + position: sticky; + } +} /* pages/duckplayer/app/components/MobileButtons.module.css */ .MobileButtons_buttons { diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.js b/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.js index 2e8aed055..2f5e4faad 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.js +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/dist/index.js @@ -1982,12 +1982,33 @@ title: "ERROR: Invalid video id", note: "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + blockedVideoErrorHeading: { + title: "YouTube won\u2019t let Duck Player load this video", + note: "Message shown when YouTube has blocked playback of a video" + }, + blockedVideoErrorMessage1: { + title: "YouTube doesn\u2019t allow this video to be viewed outside of YouTube.", + note: "Explanation on why the error is happening." + }, + blockedVideoErrorMessage2: { + title: "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "A message explaining that the blocked video can be watched directly on YouTube." + }, + signInRequiredErrorMessage1: { + title: "YouTube is blocking this video from loading. If you\u2019re using a VPN, try turning it off and reloading this page.", + note: "Explanation on why the error is happening and a suggestions on how to solve it." + }, + signInRequiredErrorMessage2: { + title: "If this doesn\u2019t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "More troubleshooting tips for this specific error" + }, tooltipInfo: { title: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } }; // pages/duckplayer/app/settings.js + var DEFAULT_SIGN_IN_REQURED_HREF = '[href*="//support.google.com/youtube/answer/3037019"]'; var Settings = class _Settings { /** * @param {object} params @@ -1995,17 +2016,20 @@ * @param {{state: 'enabled' | 'disabled'}} [params.pip] * @param {{state: 'enabled' | 'disabled'}} [params.autoplay] * @param {{state: 'enabled' | 'disabled'}} [params.focusMode] + * @param {import("../types/duckplayer.js").InitialSetupResponse['settings']['customError']} [params.customError] */ constructor({ platform = { name: "macos" }, pip = { state: "disabled" }, autoplay = { state: "enabled" }, - focusMode = { state: "enabled" } + focusMode = { state: "enabled" }, + customError = { state: "disabled", signInRequiredSelector: "" } }) { this.platform = platform; this.pip = pip; this.autoplay = autoplay; this.focusMode = focusMode; + this.customError = customError; } /** * @param {keyof import("../types/duckplayer.js").DuckPlayerPageSettings} named @@ -2014,7 +2038,7 @@ */ withFeatureState(named, settings) { if (!settings) return this; - const valid = ["pip", "autoplay", "focusMode"]; + const valid = ["pip", "autoplay", "focusMode", "customError"]; if (!valid.includes(named)) { console.warn(`Excluding invalid feature key ${named}`); return this; @@ -2053,6 +2077,28 @@ } return this; } + /** + * @param {string|null|undefined} newState + * @return {Settings} + */ + withCustomError(newState) { + if (newState === "disabled") { + return new _Settings({ + ...this, + customError: { state: "disabled" } + }); + } + if (newState === "enabled") { + return new _Settings({ + ...this, + customError: { + state: "enabled", + signOnRequiredSelector: DEFAULT_SIGN_IN_REQURED_HREF + } + }); + } + return this; + } /** * @return {string} */ @@ -2959,6 +3005,162 @@ } }; + // pages/duckplayer/app/providers/YouTubeErrorProvider.jsx + var YOUTUBE_ERROR_EVENT = "ddg-duckplayer-youtube-error"; + var YOUTUBE_ERRORS = { + ageRestricted: "age-restricted", + signInRequired: "sign-in-required", + noEmbed: "no-embed", + unknown: "unknown" + }; + var YOUTUBE_ERROR_IDS = Object.values(YOUTUBE_ERRORS); + var YouTubeErrorContext = J({ + /** @type {YouTubeError|null} */ + error: null + }); + function YouTubeErrorProvider({ initial = null, children }) { + let initialError = null; + if (initial && YOUTUBE_ERROR_IDS.includes(initial)) { + initialError = initial; + } + const [error, setError] = h2(initialError); + const messaging2 = useMessaging(); + const platformName = usePlatformName(); + const setFocusMode = useSetFocusMode(); + y2(() => { + const errorEventHandler = (event) => { + const eventError = event.detail?.error; + if (YOUTUBE_ERROR_IDS.includes(eventError) || eventError === null) { + if (eventError && eventError !== error) { + setFocusMode("paused"); + if (platformName === "macos" || platformName === "ios") { + messaging2.reportYouTubeError({ error: eventError }); + } + } else { + setFocusMode("enabled"); + } + setError(eventError); + } + }; + window.addEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + return () => window.removeEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + }, []); + return /* @__PURE__ */ g(YouTubeErrorContext.Provider, { value: { error } }, children); + } + function useYouTubeError() { + return x2(YouTubeErrorContext).error; + } + + // pages/duckplayer/app/features/error-detection.js + var ErrorDetection = class { + /** @type {HTMLIFrameElement} */ + iframe; + /** @type {CustomErrorOptions} */ + options; + /** + * @param {CustomErrorOptions} options + */ + constructor(options) { + this.options = options; + } + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + this.iframe = iframe; + if (!this.options || !this.options.signInRequiredSelector) { + console.log("Missing Custom Error options"); + return null; + } + const documentBody = iframe.contentWindow?.document?.body; + if (documentBody) { + if (this.checkForError(documentBody)) { + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + return null; + } + const observer = new MutationObserver(this.handleMutation.bind(this)); + observer.observe(documentBody, { + childList: true, + subtree: true + // Observe all descendants of the body + }); + return () => { + observer.disconnect(); + }; + } + return null; + } + /** + * Mutation handler that checks new nodes for error states + * + * @type {MutationCallback} + */ + handleMutation(mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (this.checkForError(node)) { + console.log("A node with an error has been added to the document:", node); + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + } + }); + } + } + } + /** + * Attempts to detect the type of error in the YouTube embed iframe + * @returns {YouTubeError} + */ + getErrorType() { + const iframeWindow = ( + /** @type {Window & { ytcfg: object }} */ + this.iframe.contentWindow + ); + let playerResponse; + try { + playerResponse = JSON.parse(iframeWindow.ytcfg?.get("PLAYER_VARS")?.embedded_player_response); + } catch (e3) { + console.log("Could not parse player response", e3); + } + if (typeof playerResponse === "object") { + const { + previewPlayabilityStatus: { desktopLegacyAgeGateReason, status } + } = playerResponse; + if (status === "UNPLAYABLE") { + if (desktopLegacyAgeGateReason === 1) { + return YOUTUBE_ERRORS.ageRestricted; + } + return YOUTUBE_ERRORS.noEmbed; + } + try { + if (this.options?.signInRequiredSelector && !!iframeWindow.document.querySelector(this.options.signInRequiredSelector)) { + return YOUTUBE_ERRORS.signInRequired; + } + } catch (e3) { + console.log("Sign-in required query failed", e3); + } + } + return YOUTUBE_ERRORS.unknown; + } + /** + * Analyses a node and its children to determine if it contains an error state + * + * @param {Node} [node] + */ + checkForError(node) { + if (node?.nodeType === Node.ELEMENT_NODE) { + const element = ( + /** @type {HTMLElement} */ + node + ); + return element.classList.contains("ytp-error") || !!element.querySelector("ytp-error"); + } + return false; + } + }; + // pages/duckplayer/app/features/iframe.js var IframeFeature = class { /** @@ -3015,6 +3217,12 @@ */ mouseCapture: () => { return new MouseCapture(); + }, + /** + * @return {IframeFeature} + */ + errorDetection: () => { + return new ErrorDetection(settings.customError); } }; } @@ -3083,7 +3291,8 @@ features.pip(), features.clickCapture(), features.titleCapture(), - features.mouseCapture() + features.mouseCapture(), + features.errorDetection() ]; const cleanups = []; const loadHandler = () => { @@ -3110,6 +3319,48 @@ return { ref, didLoad: () => didLoad.current = true }; } + // pages/duckplayer/app/components/YouTubeError.jsx + var import_classnames10 = __toESM(require_classnames(), 1); + + // pages/duckplayer/app/components/YouTubeError.module.css + var YouTubeError_default = { + error: "YouTubeError_error", + desktop: "YouTubeError_desktop", + mobile: "YouTubeError_mobile", + container: "YouTubeError_container", + content: "YouTubeError_content", + icon: "YouTubeError_icon", + heading: "YouTubeError_heading", + messages: "YouTubeError_messages" + }; + + // pages/duckplayer/app/components/YouTubeError.jsx + function useErrorStrings(kind) { + const { t: t3 } = useTypedTranslation(); + switch (kind) { + case "sign-in-required": + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("signInRequiredErrorMessage1"), t3("signInRequiredErrorMessage2")], + variant: "paragraphs" + }; + default: + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("blockedVideoErrorMessage1"), t3("blockedVideoErrorMessage2")], + variant: "paragraphs" + }; + } + } + function YouTubeError({ kind, layout }) { + const { heading, messages, variant } = useErrorStrings(kind); + const classes = (0, import_classnames10.default)(YouTubeError_default.error, { + [YouTubeError_default.desktop]: layout === "desktop", + [YouTubeError_default.mobile]: layout === "mobile" + }); + return /* @__PURE__ */ g("div", { className: classes }, /* @__PURE__ */ g("div", { className: YouTubeError_default.container }, /* @__PURE__ */ g("span", { className: YouTubeError_default.icon }), /* @__PURE__ */ g("div", { className: YouTubeError_default.content }, /* @__PURE__ */ g("h1", { className: YouTubeError_default.heading }, heading), messages && variant === "inline" && /* @__PURE__ */ g("p", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("span", { key: item }, item))), messages && variant === "paragraphs" && /* @__PURE__ */ g("div", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("p", { key: item }, item))), messages && variant === "list" && /* @__PURE__ */ g("ul", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("li", { key: item }, item)))))); + } + // pages/duckplayer/app/components/Components.jsx function Components() { const settings = new Settings({ @@ -3118,11 +3369,11 @@ let embed = EmbedSettings.fromHref("https://localhost?videoID=123"); let url = embed?.toEmbedUrl(); if (!url) throw new Error("unreachable"); - return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); + return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "sign-in-required" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "no-embed" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "sign-in-required" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "no-embed" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); } // pages/duckplayer/app/components/MobileApp.jsx - var import_classnames10 = __toESM(require_classnames(), 1); + var import_classnames11 = __toESM(require_classnames(), 1); // pages/duckplayer/app/components/MobileApp.module.css var MobileApp_default = { @@ -3133,7 +3384,8 @@ switch: "MobileApp_switch", embed: "MobileApp_embed", logo: "MobileApp_logo", - buttons: "MobileApp_buttons" + buttons: "MobileApp_buttons", + detachedControls: "MobileApp_detachedControls" }; // pages/duckplayer/app/features/app.js @@ -3231,11 +3483,13 @@ function MobileApp({ embed }) { const settings = useSettings(); const telemetry2 = useTelemetry(); + const youtubeError = useYouTubeError(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g( + return /* @__PURE__ */ g(k, null, !youtubeError && features.focusMode(), /* @__PURE__ */ g( OrientationProvider, { onChange: (orientation) => { + if (youtubeError) return; if (orientation === "portrait") { return FocusMode.enable(); } @@ -3251,7 +3505,10 @@ } function MobileLayout({ embed }) { const platformName = usePlatformName(); - return /* @__PURE__ */ g("main", { class: MobileApp_default.main }, /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("main", { class: MobileApp_default.main, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); } // pages/duckplayer/app/components/DesktopApp.module.css @@ -3272,10 +3529,14 @@ function DesktopApp({ embed }) { const settings = useSettings(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app }, /* @__PURE__ */ g(DesktopLayout, { embed }))); + const youtubeError = useYouTubeError(); + return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g(DesktopLayout, { embed }))); } function DesktopLayout({ embed }) { - return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); } // pages/duckplayer/app/index.js @@ -3291,7 +3552,11 @@ console.log("locale:", environment.locale); document.body.dataset.display = environment.display; const strings = environment.locale === "en" ? duckplayer_default : await getTranslationsFromStringOrLoadDynamically(init2.localeStrings, environment.locale) || duckplayer_default; - const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")); + const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withFeatureState("customError", init2.settings.customError).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")).withCustomError(baseEnvironment2.urlParams.get("customError")); + const initialYouTubeError = ( + /** @type {YouTubeError} */ + baseEnvironment2.urlParams.get("youtubeError") + ); console.log(settings); const embed = createEmbedSettings(window.location.href, settings); const didCatch = (error) => { @@ -3303,7 +3568,7 @@ if (!root) throw new Error("could not render, root element missing"); if (environment.display === "app") { D( - /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( + /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(YouTubeErrorProvider, { initial: initialYouTubeError }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( TranslationProvider, { translationObject: duckplayer_default, @@ -3319,7 +3584,7 @@ textLength: environment.textLength }, /* @__PURE__ */ g(MobileApp, { embed }) - ), /* @__PURE__ */ g(WillThrow, null))))))), + ), /* @__PURE__ */ g(WillThrow, null)))))))), root ); } else if (environment.display === "components") { @@ -3467,6 +3732,13 @@ onUserValuesChanged(cb) { return this.messaging.subscribe("onUserValuesChanged", cb); } + /** + * This will be sent if the application fails to load. + * @param {{error: import('../types/duckplayer.ts').YouTubeError}} params + */ + reportYouTubeError(params) { + this.messaging.notify("reportYouTubeError", params); + } /** * This will be sent if the application has loaded, but a client-side error * has occurred that cannot be recovered from diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/index.html b/Sources/ContentScopeScripts/dist/pages/duckplayer/index.html index de7e16964..e1771b67b 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/index.html +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/index.html @@ -85,6 +85,7 @@ /* pages/duckplayer/app/components/Components.module.css */ .Components_main { + background-color: #000; color: white; max-width: 3840px; margin: 0 auto; @@ -136,7 +137,7 @@ text-decoration: none; } [data-layout=mobile] .Button_button { - background-color: #2f2f2f; + background-color: rgba(255, 255, 255, 0.12); } .Button_button:hover, .Button_button:focus-visible { @@ -212,7 +213,7 @@ .SwitchBarMobile_switchBar { display: grid; border-radius: 8px; - background: #2f2f2f; + background: rgba(255, 255, 255, 0.12); padding-inline: 16px; height: 100%; line-height: 1.1; @@ -542,6 +543,9 @@ .Wordmark_mobile_logo { height: 100px; } + [data-youtube-error=true] .Wordmark_mobile_logo { + height: 44px; + } } .Wordmark_mobile_logoSvg img { display: block; @@ -613,6 +617,127 @@ } } +/* pages/duckplayer/app/components/YouTubeError.module.css */ +.YouTubeError_error { + align-items: center; + background: rgba(0, 0, 0, 0.6); + display: grid; + height: 100%; + justify-items: center; +} +.YouTubeError_error.YouTubeError_desktop { + height: var(--frame-height); + overflow: hidden; + position: relative; + z-index: 1; +} +.YouTubeError_error.YouTubeError_mobile { + border-radius: var(--inner-radius); + height: 100%; + overflow: auto; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_error.YouTubeError_mobile { + aspect-ratio: 16 / 9; + } +} +.YouTubeError_desktop { + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.YouTubeError_container { + column-gap: 24px; + display: flex; + flex-flow: row; + margin: 0; + max-width: 680px; + padding: 0 40px; + row-gap: 4px; +} +.YouTubeError_mobile .YouTubeError_container { + flex-flow: column; + padding: 0 24px; +} +@media screen and (min-height: 320px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 16px 0; + } +} +@media screen and (min-width: 375px) and (min-height: 400px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 36px 0; + } +} +.YouTubeError_content { + display: flex; + flex-direction: column; + gap: 4px; + margin: 16px 0; +} +@media screen and (min-width: 600px) { + .YouTubeError_content { + margin: 24px 0; + } +} +.YouTubeError_icon { + align-self: center; + display: flex; + justify-content: center; +} +.YouTubeError_icon::before { + content: " "; + display: block; + background: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A%0A') no-repeat; + height: 48px; + width: 48px; +} +@media screen and (max-width: 320px) { + .YouTubeError_icon { + display: none; + } +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_icon { + justify-content: start; + } + .YouTubeError_icon::before { + background-image: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A %0A%0A'); + height: 96px; + width: 128px; + } +} +.YouTubeError_heading { + color: #fff; + font-size: 20px; + font-weight: 700; + line-height: calc(24 / 20); + margin: 0; +} +.YouTubeError_messages { + color: #ccc; + font-size: 16px; + line-height: calc(24 / 16); +} +div.YouTubeError_messages { + display: flex; + flex-direction: column; + gap: 24px; +} +div.YouTubeError_messages p { + margin: 0; +} +p.YouTubeError_messages { + margin: 0; +} +ul.YouTubeError_messages li { + list-style: disc; + margin-left: 24px; +} + /* pages/duckplayer/app/components/MobileApp.module.css */ body[data-display=app] { padding: 8px; @@ -650,6 +775,7 @@ --inner-radius: 12px; --logo-width: 157px; --inner-padding: 8px; + --mobile-buttons-padding: 8px; position: relative; max-width: 100vh; margin: 0 auto; @@ -705,6 +831,15 @@ grid-area: switch; height: 44px; } +.MobileApp_detachedControls { + grid-area: detached; + display: flex; + flex-flow: column; + gap: 8px; + padding: 8px; + background: #2f2f2f; + border-radius: 12px; +} @media screen and (min-width: 425px) and (max-height: 600px) { .MobileApp_main { grid-template-rows: max-content auto max-content max-content 12px max-content auto; @@ -798,6 +933,71 @@ justify-content: end; } } +@media screen and (max-width: 599px) { + .MobileApp_main[data-youtube-error=true] { + --bg-color: transparent; + --inner-padding: 4px; + grid-template-areas: "logo" "gap3" "embed" "gap4" "switch" "buttons"; + grid-template-rows: max-content 16px auto 12px max-content max-content; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + background: #2f2f2f; + border-radius: var(--outer-radius); + padding: 4px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_switch { + background: #2f2f2f; + padding: 8px 8px 0 8px; + height: 60px; + max-height: 60px; + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + background: #2f2f2f; + padding: 8px; + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_buttons { + border-radius: var(--outer-radius); + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_switch { + background: transparent; + max-height: 0; + } +} +@media screen and (max-width: 599px) and (max-height: 599px) { + .MobileApp_main[data-youtube-error=true] { + max-width: unset; + grid-template-rows: 0 0 auto 12px 0 max-content; + } + .MobileApp_main[data-youtube-error=true] :is(.MobileApp_logo, .MobileApp_switch) { + display: none; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + } +} +@media screen and (min-width: 600px) and (max-height: 450px) { + .MobileApp_main[data-youtube-error=true] { + grid-template-areas: "embed" "buttons" "gap5"; + grid-template-rows: auto max-content 8px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + display: block; + } +} +@media screen and (max-height: 320px) { + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + overflow-y: auto; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + bottom: 0; + position: sticky; + } +} /* pages/duckplayer/app/components/MobileButtons.module.css */ .MobileButtons_buttons { @@ -2916,12 +3116,33 @@ title: "ERROR: Invalid video id", note: "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + blockedVideoErrorHeading: { + title: "YouTube won\u2019t let Duck Player load this video", + note: "Message shown when YouTube has blocked playback of a video" + }, + blockedVideoErrorMessage1: { + title: "YouTube doesn\u2019t allow this video to be viewed outside of YouTube.", + note: "Explanation on why the error is happening." + }, + blockedVideoErrorMessage2: { + title: "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "A message explaining that the blocked video can be watched directly on YouTube." + }, + signInRequiredErrorMessage1: { + title: "YouTube is blocking this video from loading. If you\u2019re using a VPN, try turning it off and reloading this page.", + note: "Explanation on why the error is happening and a suggestions on how to solve it." + }, + signInRequiredErrorMessage2: { + title: "If this doesn\u2019t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "More troubleshooting tips for this specific error" + }, tooltipInfo: { title: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } }; // pages/duckplayer/app/settings.js + var DEFAULT_SIGN_IN_REQURED_HREF = '[href*="//support.google.com/youtube/answer/3037019"]'; var Settings = class _Settings { /** * @param {object} params @@ -2929,17 +3150,20 @@ * @param {{state: 'enabled' | 'disabled'}} [params.pip] * @param {{state: 'enabled' | 'disabled'}} [params.autoplay] * @param {{state: 'enabled' | 'disabled'}} [params.focusMode] + * @param {import("../types/duckplayer.js").InitialSetupResponse['settings']['customError']} [params.customError] */ constructor({ platform = { name: "macos" }, pip = { state: "disabled" }, autoplay = { state: "enabled" }, - focusMode = { state: "enabled" } + focusMode = { state: "enabled" }, + customError = { state: "disabled", signInRequiredSelector: "" } }) { this.platform = platform; this.pip = pip; this.autoplay = autoplay; this.focusMode = focusMode; + this.customError = customError; } /** * @param {keyof import("../types/duckplayer.js").DuckPlayerPageSettings} named @@ -2948,7 +3172,7 @@ */ withFeatureState(named, settings) { if (!settings) return this; - const valid = ["pip", "autoplay", "focusMode"]; + const valid = ["pip", "autoplay", "focusMode", "customError"]; if (!valid.includes(named)) { console.warn(`Excluding invalid feature key ${named}`); return this; @@ -2987,6 +3211,28 @@ } return this; } + /** + * @param {string|null|undefined} newState + * @return {Settings} + */ + withCustomError(newState) { + if (newState === "disabled") { + return new _Settings({ + ...this, + customError: { state: "disabled" } + }); + } + if (newState === "enabled") { + return new _Settings({ + ...this, + customError: { + state: "enabled", + signOnRequiredSelector: DEFAULT_SIGN_IN_REQURED_HREF + } + }); + } + return this; + } /** * @return {string} */ @@ -3893,6 +4139,162 @@ } }; + // pages/duckplayer/app/providers/YouTubeErrorProvider.jsx + var YOUTUBE_ERROR_EVENT = "ddg-duckplayer-youtube-error"; + var YOUTUBE_ERRORS = { + ageRestricted: "age-restricted", + signInRequired: "sign-in-required", + noEmbed: "no-embed", + unknown: "unknown" + }; + var YOUTUBE_ERROR_IDS = Object.values(YOUTUBE_ERRORS); + var YouTubeErrorContext = J({ + /** @type {YouTubeError|null} */ + error: null + }); + function YouTubeErrorProvider({ initial = null, children }) { + let initialError = null; + if (initial && YOUTUBE_ERROR_IDS.includes(initial)) { + initialError = initial; + } + const [error, setError] = h2(initialError); + const messaging2 = useMessaging(); + const platformName = usePlatformName(); + const setFocusMode = useSetFocusMode(); + y2(() => { + const errorEventHandler = (event) => { + const eventError = event.detail?.error; + if (YOUTUBE_ERROR_IDS.includes(eventError) || eventError === null) { + if (eventError && eventError !== error) { + setFocusMode("paused"); + if (platformName === "macos" || platformName === "ios") { + messaging2.reportYouTubeError({ error: eventError }); + } + } else { + setFocusMode("enabled"); + } + setError(eventError); + } + }; + window.addEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + return () => window.removeEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + }, []); + return /* @__PURE__ */ g(YouTubeErrorContext.Provider, { value: { error } }, children); + } + function useYouTubeError() { + return x2(YouTubeErrorContext).error; + } + + // pages/duckplayer/app/features/error-detection.js + var ErrorDetection = class { + /** @type {HTMLIFrameElement} */ + iframe; + /** @type {CustomErrorOptions} */ + options; + /** + * @param {CustomErrorOptions} options + */ + constructor(options) { + this.options = options; + } + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + this.iframe = iframe; + if (!this.options || !this.options.signInRequiredSelector) { + console.log("Missing Custom Error options"); + return null; + } + const documentBody = iframe.contentWindow?.document?.body; + if (documentBody) { + if (this.checkForError(documentBody)) { + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + return null; + } + const observer = new MutationObserver(this.handleMutation.bind(this)); + observer.observe(documentBody, { + childList: true, + subtree: true + // Observe all descendants of the body + }); + return () => { + observer.disconnect(); + }; + } + return null; + } + /** + * Mutation handler that checks new nodes for error states + * + * @type {MutationCallback} + */ + handleMutation(mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (this.checkForError(node)) { + console.log("A node with an error has been added to the document:", node); + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + } + }); + } + } + } + /** + * Attempts to detect the type of error in the YouTube embed iframe + * @returns {YouTubeError} + */ + getErrorType() { + const iframeWindow = ( + /** @type {Window & { ytcfg: object }} */ + this.iframe.contentWindow + ); + let playerResponse; + try { + playerResponse = JSON.parse(iframeWindow.ytcfg?.get("PLAYER_VARS")?.embedded_player_response); + } catch (e3) { + console.log("Could not parse player response", e3); + } + if (typeof playerResponse === "object") { + const { + previewPlayabilityStatus: { desktopLegacyAgeGateReason, status } + } = playerResponse; + if (status === "UNPLAYABLE") { + if (desktopLegacyAgeGateReason === 1) { + return YOUTUBE_ERRORS.ageRestricted; + } + return YOUTUBE_ERRORS.noEmbed; + } + try { + if (this.options?.signInRequiredSelector && !!iframeWindow.document.querySelector(this.options.signInRequiredSelector)) { + return YOUTUBE_ERRORS.signInRequired; + } + } catch (e3) { + console.log("Sign-in required query failed", e3); + } + } + return YOUTUBE_ERRORS.unknown; + } + /** + * Analyses a node and its children to determine if it contains an error state + * + * @param {Node} [node] + */ + checkForError(node) { + if (node?.nodeType === Node.ELEMENT_NODE) { + const element = ( + /** @type {HTMLElement} */ + node + ); + return element.classList.contains("ytp-error") || !!element.querySelector("ytp-error"); + } + return false; + } + }; + // pages/duckplayer/app/features/iframe.js var IframeFeature = class { /** @@ -3949,6 +4351,12 @@ */ mouseCapture: () => { return new MouseCapture(); + }, + /** + * @return {IframeFeature} + */ + errorDetection: () => { + return new ErrorDetection(settings.customError); } }; } @@ -4017,7 +4425,8 @@ features.pip(), features.clickCapture(), features.titleCapture(), - features.mouseCapture() + features.mouseCapture(), + features.errorDetection() ]; const cleanups = []; const loadHandler = () => { @@ -4044,6 +4453,48 @@ return { ref, didLoad: () => didLoad.current = true }; } + // pages/duckplayer/app/components/YouTubeError.jsx + var import_classnames10 = __toESM(require_classnames(), 1); + + // pages/duckplayer/app/components/YouTubeError.module.css + var YouTubeError_default = { + error: "YouTubeError_error", + desktop: "YouTubeError_desktop", + mobile: "YouTubeError_mobile", + container: "YouTubeError_container", + content: "YouTubeError_content", + icon: "YouTubeError_icon", + heading: "YouTubeError_heading", + messages: "YouTubeError_messages" + }; + + // pages/duckplayer/app/components/YouTubeError.jsx + function useErrorStrings(kind) { + const { t: t3 } = useTypedTranslation(); + switch (kind) { + case "sign-in-required": + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("signInRequiredErrorMessage1"), t3("signInRequiredErrorMessage2")], + variant: "paragraphs" + }; + default: + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("blockedVideoErrorMessage1"), t3("blockedVideoErrorMessage2")], + variant: "paragraphs" + }; + } + } + function YouTubeError({ kind, layout }) { + const { heading, messages, variant } = useErrorStrings(kind); + const classes = (0, import_classnames10.default)(YouTubeError_default.error, { + [YouTubeError_default.desktop]: layout === "desktop", + [YouTubeError_default.mobile]: layout === "mobile" + }); + return /* @__PURE__ */ g("div", { className: classes }, /* @__PURE__ */ g("div", { className: YouTubeError_default.container }, /* @__PURE__ */ g("span", { className: YouTubeError_default.icon }), /* @__PURE__ */ g("div", { className: YouTubeError_default.content }, /* @__PURE__ */ g("h1", { className: YouTubeError_default.heading }, heading), messages && variant === "inline" && /* @__PURE__ */ g("p", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("span", { key: item }, item))), messages && variant === "paragraphs" && /* @__PURE__ */ g("div", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("p", { key: item }, item))), messages && variant === "list" && /* @__PURE__ */ g("ul", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("li", { key: item }, item)))))); + } + // pages/duckplayer/app/components/Components.jsx function Components() { const settings = new Settings({ @@ -4052,11 +4503,11 @@ let embed = EmbedSettings.fromHref("https://localhost?videoID=123"); let url = embed?.toEmbedUrl(); if (!url) throw new Error("unreachable"); - return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); + return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "sign-in-required" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "no-embed" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "sign-in-required" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "no-embed" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); } // pages/duckplayer/app/components/MobileApp.jsx - var import_classnames10 = __toESM(require_classnames(), 1); + var import_classnames11 = __toESM(require_classnames(), 1); // pages/duckplayer/app/components/MobileApp.module.css var MobileApp_default = { @@ -4067,7 +4518,8 @@ switch: "MobileApp_switch", embed: "MobileApp_embed", logo: "MobileApp_logo", - buttons: "MobileApp_buttons" + buttons: "MobileApp_buttons", + detachedControls: "MobileApp_detachedControls" }; // pages/duckplayer/app/features/app.js @@ -4165,11 +4617,13 @@ function MobileApp({ embed }) { const settings = useSettings(); const telemetry2 = useTelemetry(); + const youtubeError = useYouTubeError(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g( + return /* @__PURE__ */ g(k, null, !youtubeError && features.focusMode(), /* @__PURE__ */ g( OrientationProvider, { onChange: (orientation) => { + if (youtubeError) return; if (orientation === "portrait") { return FocusMode.enable(); } @@ -4185,7 +4639,10 @@ } function MobileLayout({ embed }) { const platformName = usePlatformName(); - return /* @__PURE__ */ g("main", { class: MobileApp_default.main }, /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("main", { class: MobileApp_default.main, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); } // pages/duckplayer/app/components/DesktopApp.module.css @@ -4206,10 +4663,14 @@ function DesktopApp({ embed }) { const settings = useSettings(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app }, /* @__PURE__ */ g(DesktopLayout, { embed }))); + const youtubeError = useYouTubeError(); + return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g(DesktopLayout, { embed }))); } function DesktopLayout({ embed }) { - return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); } // pages/duckplayer/app/index.js @@ -4225,7 +4686,11 @@ console.log("locale:", environment.locale); document.body.dataset.display = environment.display; const strings = environment.locale === "en" ? duckplayer_default : await getTranslationsFromStringOrLoadDynamically(init2.localeStrings, environment.locale) || duckplayer_default; - const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")); + const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withFeatureState("customError", init2.settings.customError).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")).withCustomError(baseEnvironment2.urlParams.get("customError")); + const initialYouTubeError = ( + /** @type {YouTubeError} */ + baseEnvironment2.urlParams.get("youtubeError") + ); console.log(settings); const embed = createEmbedSettings(window.location.href, settings); const didCatch = (error) => { @@ -4237,7 +4702,7 @@ if (!root) throw new Error("could not render, root element missing"); if (environment.display === "app") { D( - /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( + /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(YouTubeErrorProvider, { initial: initialYouTubeError }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( TranslationProvider, { translationObject: duckplayer_default, @@ -4253,7 +4718,7 @@ textLength: environment.textLength }, /* @__PURE__ */ g(MobileApp, { embed }) - ), /* @__PURE__ */ g(WillThrow, null))))))), + ), /* @__PURE__ */ g(WillThrow, null)))))))), root ); } else if (environment.display === "components") { @@ -4401,6 +4866,13 @@ onUserValuesChanged(cb) { return this.messaging.subscribe("onUserValuesChanged", cb); } + /** + * This will be sent if the application fails to load. + * @param {{error: import('../types/duckplayer.ts').YouTubeError}} params + */ + reportYouTubeError(params) { + this.messaging.notify("reportYouTubeError", params); + } /** * This will be sent if the application has loaded, but a client-side error * has occurred that cannot be recovered from diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/bg/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/bg/duckplayer.json index d7f3d20ef..c807a2f78 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/bg/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/bg/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ГРЕШКА: невалиден идентификатор на видеоклипа", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube няма да позволи на Duck Player да зареди това видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволява това видео да бъде гледано извън YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Все пак можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокира зареждането на това видео. Ако използвате VPN, опитайте да го изключите и презаредете тази страница.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ако това не свърши работа, можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player осигурява чисто изживяване без персонализирани реклами в YouTube и предотвратява влиянието на вече гледаните видеоклипове върху препоръките на YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/cs/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/cs/duckplayer.json index 8d24c5726..690569a5d 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/cs/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/cs/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatné ID videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovoluje přehrávači Duck Player načíst tohle video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nedovoluje spuštění videa mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Na tohle video se můžeš pořád podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítání tohohle videa. Pokud používáš VPN, zkus ji vypnout a stránku znovu načíst.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Pokud to nefunguje, můžeš se na video podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Přehrávač Duck Player nabízí sledování v minimalistickém prostředí bez personalizovaných reklam a brání tomu, aby sledovaná videa ovlivňovala tvoje doporučení na YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/da/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/da/duckplayer.json index f99ab298e..aae6004c2 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/da/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/da/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEJL: Ugyldigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube vil ikke lade Duck Player indlæse denne video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillader ikke, at denne video vises uden for YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokerer for, at denne video kan indlæses. Hvis du bruger en VPN, så prøv at slå den fra og genindlæse denne side.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis dette ikke virker, kan du stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player giver en ren seeroplevelse uden målrettede annoncer og forhindrer, at visningsaktivitet påvirker dine YouTube-anbefalinger." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/de/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/de/duckplayer.json index 0ddca103a..abd163119 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/de/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/de/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEHLER: Ungültige Video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lässt den Duck Player dieses Video nicht laden", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube erlaubt nicht, dass dieses Video außerhalb von YouTube angesehen wird.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kannst dieses Video auf YouTube ansehen, aber ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockiert das Laden dieses Videos. Falls du ein VPN benutzt, deaktiviere es und lade diese Seite neu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Falls das nicht funktioniert, kannst du das Video dennoch auf YouTube ansehen, jedoch ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/el/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/el/duckplayer.json index d00195f5e..2d8fa43c5 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/el/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/el/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ΣΦΑΛΜΑ: Μη έγκυρο αναγνωριστικό βίντεο", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "Το YouTube δεν θα αφήσει το Duck Player να φορτώσει το βίντεο αυτό", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Το YouTube δεν επιτρέπει την προβολή αυτού του βίντεο εκτός YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Μπορείτε ακόμα να παρακολουθήσετε αυτό το βίντεο στο YouTube, αλλά χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "Το YouTube μπλοκάρει τη φόρτωση αυτού του βίντεο. Εάν χρησιμοποιείτε VPN, δοκιμάστε να το απενεργοποιήσετε και να φορτώσετε εκ νέου αυτήν τη σελίδα.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Εάν δεν λειτουργήσει αυτό, μπορείτε να παρακολουθήσετε αυτό το βίντεο στο YouTube, ωστόσο χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Το Duck Player παρέχει μια καθαρή εμπειρία προβολής χωρίς εξατομικευμένες διαφημίσεις, ενώ εμποδίζει τη δραστηριότητα προβολής να επηρεάσει τις συστάσεις που θα λαμβάνετε στο YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/en/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/en/duckplayer.json index c2b5683b9..fe94917c4 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/en/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/en/duckplayer.json @@ -33,6 +33,26 @@ "title": "ERROR: Invalid video id", "note": "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading": { + "title": "YouTube won’t let Duck Player load this video", + "note": "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1": { + "title": "YouTube doesn’t allow this video to be viewed outside of YouTube.", + "note": "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2": { + "title": "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1": { + "title": "YouTube is blocking this video from loading. If you’re using a VPN, try turning it off and reloading this page.", + "note": "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2": { + "title": "If this doesn’t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "More troubleshooting tips for this specific error" + }, "tooltipInfo": { "title": "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/es/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/es/duckplayer.json index 1b5d8b958..f5af0d046 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/es/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/es/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERROR: ID de vídeo no válida", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube no permite que Duck Player cargue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube no permite que este vídeo se vea fuera de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional que ofrece Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube está bloqueando la carga de este vídeo. Si estás usando una VPN, intenta desactivarla y volver a cargar la página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si esto no funciona, sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/et/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/et/duckplayer.json index c9863f4f5..70b30efee 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/et/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/et/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIGA: vale video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei luba Duck Playeril seda videot laadida", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei luba seda videot väljaspool YouTube'i vaadata.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Saate seda videot endiselt YouTube'is vaadata, kuid ilma Duck Player'i lisatud privaatsuseta.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokeerib selle video laadimise. Kui kasutate VPN-i, proovige see välja lülitada ning leht uuesti laadida.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Kui see ei aita, saate seda videot ikkagi YouTube'is vaadata, kuid ilma Duck Playeri lisatud privaatsuseta.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pakub isikupärastatud reklaamidest vaba vaatamiskogemust ja takistab, et vaatamisaktiivsus mõjutaks sinu YouTube'i soovitusi." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fi/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fi/duckplayer.json index e73022b8f..4c830a739 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fi/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fi/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIRHE: virheellinen videotunnus", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei salli Duck Playerin ladata tätä videota.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei salli tämän videon katsomista YouTuben ulkopuolella.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Voit yhä katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube estää tämän videon latautumisen. Jos käytät VPN:ää, kytke se pois päältä ja lataa tämä sivu uudelleen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jos tämä ei toimi, voit silti katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tarjoaa puhtaan katselukokemuksen ilman kohdennettuja mainoksia ja estää katseluhistoriaa vaikuttamasta YouTube-suosituksiisi." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fr/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fr/duckplayer.json index 716f0c071..12eb0ac92 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fr/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/fr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERREUR : identifiant vidéo non valide", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne permet pas à Duck Player de charger cette vidéo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube n'autorise pas le visionnage de cette vidéo en dehors de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloque le chargement de cette vidéo. Si vous utilisez un VPN, essayez de le désactiver et de recharger cette page.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si cela ne fonctionne pas, vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hr/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hr/duckplayer.json index 3f0e8aeae..48fdbf997 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hr/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "POGREŠKA: Nevažeći ID videozapisa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dopušta Duck Playeru da učita ovaj videozapis.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dopušta da se ovaj videozapis gleda izvan YouTubea.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Još uvijek možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokira učitavanje ovog videozapisa. Ako koristiš VPN, pokušaj ga isključiti i ponovno učitati ovu stranicu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ako to ne uspije, i dalje možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pruža čisti doživljaj gledanja bez personaliziranih oglasa i sprječava da aktivnosti gledanja utječu na tvoje preporuke na YouTubeu." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hu/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hu/duckplayer.json index 3bbe06210..c8faf475f 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hu/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/hu/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HIBA: Érvénytelen videoazonosító", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "A YouTube nem engedi, hogy a Duck Player betöltse ezt a videót", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "A YouTube nem engedi, hogy ezt a videót a YouTube-on kívül nézd meg.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Megnézheted a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "A YouTube blokkolja ennek a videónak a betöltését. Ha VPN-t használsz, próbáld meg, hogy kikapcsolod, majd újra betöltöd ezt az oldalt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ha ez nem működik, akkor is megnézheted ezt a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "A Duck Player személyre szabott hirdetések nélküli, letisztult megtekintési élményt nyújt, és megakadályozza, hogy a megtekintési tevékenységed befolyásolja a neked szóló YouTube-ajánlásokat." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/it/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/it/duckplayer.json index 9ce216677..464303fca 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/it/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/it/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRORE: ID video non valido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube non consente a Duck Player di caricare questo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Questo video si può vedere solo su YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Puoi ancora guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube sta impedendo il caricamento di questo video. Se stai utilizzando una VPN, prova a disattivarla e a ricaricare questa pagina.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se il problema non si risolve, puoi comunque guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lt/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lt/duckplayer.json index 1b282dd2c..c8dd25e38 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lt/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KLAIDA: netinkamas vaizdo įrašo ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "„YouTube“ neleidžia „Duck Player“ įkelti šio vaizdo įrašo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "„YouTube“ neleidžia šio vaizdo įrašo žiūrėti ne „YouTube“ platformoje.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šį vaizdo įrašą vis dar gali žiūrėti „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "„YouTube“ blokuoja šio vaizdo įrašo įkėlimą. Jei naudoji VPN, pabandyk jį išjungti ir iš naujo įkelti šį puslapį.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jei tai neveikia, vis tiek gali žiūrėti šį vaizdo įrašą „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "„Duck Player“ užtikrina nepriekaištingą žiūrėjimo patirtį be suasmenintų reklamų ir neleidžia žiūrėjimo veiklai daryti įtakos „YouTube“ rekomendacijoms." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lv/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lv/duckplayer.json index 46d0bee8e..3ae376c31 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lv/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/lv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KĻŪDA: Nederīgs video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube neļauj Duck Player ielādēt šo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube neļauj skatīties šo video ārpus YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šo videoklipu joprojām vari skatīties vietnē YouTube, taču bez papildu Duck Player konfidencialitātes.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloķē šī video ielādi. Ja tu izmanto VPN, mēģini to izslēgt un pārlādēt šo lapu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ja tas nedarbojas, joprojām vari skatīties šo video vietnē YouTube, taču bez papildu privātuma, ko nodrošina Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player nodrošina netraucētu skatīšanās pieredzi bez personalizētām reklāmām un neļauj skatīšanās darbībām ietekmēt tavus YouTube ieteikumus." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nb/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nb/duckplayer.json index 4c1d826f6..fef475447 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nb/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nb/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEIL: Ugyldig video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lar ikke Duck Player laste denne videoen", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillater ikke visning av denne videoen utenfor YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fremdeles se videoen på YouTube, men uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkerer denne videoen fra å lastes. Hvis du bruker en VPN, kan du prøve å slå den av og laste denne siden på nytt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis ikke det virker, kan du fremdeles se videoen på YouTube, bare uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tilbyr en ren seeropplevelse uten tilpassede annonser og forhindrer at seeraktiviteten din påvirker YouTube-anbefalingene dine." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nl/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nl/duckplayer.json index a1be8669e..51a0ece78 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nl/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/nl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FOUT: ongeldige video-id", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube staat Duck Player niet toe om deze video te laden.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube staat niet toe dat je deze video buiten YouTube bekijkt.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Je kunt deze video nog steeds bekijken op YouTube, maar zonder de extra privacy van Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkeert het laden van deze video. Als je een VPN gebruikt, probeer deze dan uit te schakelen en deze pagina opnieuw te laden.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Als dit niet werkt, kun je deze video nog steeds op YouTube bekijken, maar dan zonder de extra privacy van Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pl/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pl/duckplayer.json index accd3fde5..8b633d415 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pl/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "BŁĄD: nieprawidłowy identyfikator filmu", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nie zezwala na załadowanie tego filmu przez Duck Player", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nie zezwala na oglądanie tego filmu poza YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Nadal możesz oglądać ten film na YouTube, ale bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje ładowanie tego filmu. Jeśli korzystasz z sieci VPN, spróbuj ją wyłączyć i ponownie załadować tę stronę.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jeśli to nie pomoże, nadal możesz oglądać ten film na YouTube, jednak bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pt/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pt/duckplayer.json index a5bfca188..2ff6fad9c 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pt/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/pt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRO: ID de vídeo inválido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "O YouTube não permite que o Duck Player carregue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "O YouTube não permite que vejas este vídeo fora do YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "O YouTube está a bloquear o carregamento deste vídeo. Se estiveres a usar uma VPN, tenta desativá-la e recarregar esta página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se isto não funcionar, continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ro/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ro/duckplayer.json index bfafec70e..25ffac535 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ro/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ro/duckplayer.json @@ -32,6 +32,26 @@ "title" : "EROARE: ID video incorect", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nu permite Duck Player să încarce acest videoclip", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nu permite ca acest videoclip să fie vizionat în afara YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blochează încărcarea acestui videoclip. Dacă folosești un VPN, încearcă să-l dezactivezi și să reîncarci această pagină.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Dacă acest lucru nu funcționează, poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player oferă o experiență de vizionare fără perturbări, fără reclame personalizate și împiedică activitatea de vizionare să îți influențeze recomandările YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ru/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ru/duckplayer.json index 4bf5cc0c1..d1a3fc528 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ru/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/ru/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ОШИБКА: Неверный идентификатор видео", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube не позволяет проигрывателю Duck Player загрузить это видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволяет смотреть это видео вне своей платформы.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Вы по-прежнему можете посмотреть этот ролик на YouTube, но уже без дополнительной защиты Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокирует загрузку этого видео. Если вы используете VPN, отключите ее и перезагрузите страницу.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Если это не даст результата, вы все равно сможете просмотреть это видео на YouTube, но без дополнительной защиты конфиденциальности, обеспечиваемой Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sk/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sk/duckplayer.json index 88a2cc3a5..a68cdeb89 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sk/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sk/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatný identifikátor videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovolí Duck Playeru načítať toto video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nepovoľuje, aby sa toto video pozeralo mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Toto video si môžeš pozrieť aj na YouTube, ale bez dodatočného súkromia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítanie tohto videa. Ak používaš sieť VPN, skús ju vypnúť a znova načítať túto stránku.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ak to nefunguje, môžeš si toto video pozrieť aj na YouTube, ale bez dodatočnej ochrany súkromia, ktorú poskytuje Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player poskytuje čisté zobrazenie bez personalizovaných reklám a zabraňuje tomu, aby aktivita pri sledovaní ovplyvňovala vaše odporúčania v službe YouTube." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sl/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sl/duckplayer.json index 7d4a89155..977830ef4 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sl/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "NAPAKA: Neveljaven ID videoposnetka", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dovoli predvajalniku Duck Player naložiti tega videa", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dovoljuje, da si ta videoposnetek ogledate zunaj YouTuba.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Ta videoposnetek si lahko še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube preprečuje nalaganje tega videoposnetka. Če uporabljate omrežje VPN, ga poskusite izklopiti in znova naložite to stran.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Če to ne deluje, si lahko ta videoposnetek še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Predvajalnik Duck Player zagotavlja čisto izkušnjo gledanja brez prilagojenih oglasov in preprečuje, da bi dejavnost gledanja vplivala na vaša priporočila v YouTubu." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sv/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sv/duckplayer.json index cb5b75e4c..444e91750 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sv/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/sv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEL: Ogiltigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube låter inte Duck Player läsa in den här videon", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillåter inte att den här videon ses utanför YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockerar den här videon från att läsas in. Om du använder ett VPN kan du prova stänga av det och läsa in sidan igen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Om det inte fungerar kan du fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ger en störningsfri visningsupplevelse utan personliga annonser och förhindrar att din tittaraktivitet påverkar YouTube-rekommendationer." } diff --git a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/tr/duckplayer.json b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/tr/duckplayer.json index c23cab2bb..e733fcaa6 100644 --- a/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/tr/duckplayer.json +++ b/Sources/ContentScopeScripts/dist/pages/duckplayer/locales/tr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HATA: Geçersiz video kimliği", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube, Duck Player'ın bu videoyu yüklemesine izin vermiyor", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube, bu videonun YouTube dışında izlenmesine izin vermiyor.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Bu videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bu videonun yüklenmesini engelliyor. VPN kullanıyorsanız, VPN'i kapatıp bu sayfayı yeniden yüklemeyi deneyin.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Bu işe yaramazsa, videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player, kişiselleştirilmiş reklamlar olmadan temiz bir görüntüleme deneyimi sağlar ve görüntüleme etkinliğinin YouTube önerilerinizi etkilemesini önler." } diff --git a/build/android/pages/duckplayer/dist/index.css b/build/android/pages/duckplayer/dist/index.css index cff98b790..7e8fbf3a0 100644 --- a/build/android/pages/duckplayer/dist/index.css +++ b/build/android/pages/duckplayer/dist/index.css @@ -61,6 +61,7 @@ body[data-display=app] { /* pages/duckplayer/app/components/Components.module.css */ .Components_main { + background-color: #000; color: white; max-width: 3840px; margin: 0 auto; @@ -112,7 +113,7 @@ body[data-display=app] { text-decoration: none; } [data-layout=mobile] .Button_button { - background-color: #2f2f2f; + background-color: rgba(255, 255, 255, 0.12); } .Button_button:hover, .Button_button:focus-visible { @@ -188,7 +189,7 @@ body[data-display=app] { .SwitchBarMobile_switchBar { display: grid; border-radius: 8px; - background: #2f2f2f; + background: rgba(255, 255, 255, 0.12); padding-inline: 16px; height: 100%; line-height: 1.1; @@ -518,6 +519,9 @@ body[data-display=app] { .Wordmark_mobile_logo { height: 100px; } + [data-youtube-error=true] .Wordmark_mobile_logo { + height: 44px; + } } .Wordmark_mobile_logoSvg img { display: block; @@ -589,6 +593,127 @@ body[data-display=app] { } } +/* pages/duckplayer/app/components/YouTubeError.module.css */ +.YouTubeError_error { + align-items: center; + background: rgba(0, 0, 0, 0.6); + display: grid; + height: 100%; + justify-items: center; +} +.YouTubeError_error.YouTubeError_desktop { + height: var(--frame-height); + overflow: hidden; + position: relative; + z-index: 1; +} +.YouTubeError_error.YouTubeError_mobile { + border-radius: var(--inner-radius); + height: 100%; + overflow: auto; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_error.YouTubeError_mobile { + aspect-ratio: 16 / 9; + } +} +.YouTubeError_desktop { + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.YouTubeError_container { + column-gap: 24px; + display: flex; + flex-flow: row; + margin: 0; + max-width: 680px; + padding: 0 40px; + row-gap: 4px; +} +.YouTubeError_mobile .YouTubeError_container { + flex-flow: column; + padding: 0 24px; +} +@media screen and (min-height: 320px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 16px 0; + } +} +@media screen and (min-width: 375px) and (min-height: 400px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 36px 0; + } +} +.YouTubeError_content { + display: flex; + flex-direction: column; + gap: 4px; + margin: 16px 0; +} +@media screen and (min-width: 600px) { + .YouTubeError_content { + margin: 24px 0; + } +} +.YouTubeError_icon { + align-self: center; + display: flex; + justify-content: center; +} +.YouTubeError_icon::before { + content: " "; + display: block; + background: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A%0A') no-repeat; + height: 48px; + width: 48px; +} +@media screen and (max-width: 320px) { + .YouTubeError_icon { + display: none; + } +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_icon { + justify-content: start; + } + .YouTubeError_icon::before { + background-image: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A %0A%0A'); + height: 96px; + width: 128px; + } +} +.YouTubeError_heading { + color: #fff; + font-size: 20px; + font-weight: 700; + line-height: calc(24 / 20); + margin: 0; +} +.YouTubeError_messages { + color: #ccc; + font-size: 16px; + line-height: calc(24 / 16); +} +div.YouTubeError_messages { + display: flex; + flex-direction: column; + gap: 24px; +} +div.YouTubeError_messages p { + margin: 0; +} +p.YouTubeError_messages { + margin: 0; +} +ul.YouTubeError_messages li { + list-style: disc; + margin-left: 24px; +} + /* pages/duckplayer/app/components/MobileApp.module.css */ body[data-display=app] { padding: 8px; @@ -626,6 +751,7 @@ html[data-focus-mode=on] .MobileApp_hideInFocus { --inner-radius: 12px; --logo-width: 157px; --inner-padding: 8px; + --mobile-buttons-padding: 8px; position: relative; max-width: 100vh; margin: 0 auto; @@ -681,6 +807,15 @@ html[data-focus-mode=on] .MobileApp_embed { grid-area: switch; height: 44px; } +.MobileApp_detachedControls { + grid-area: detached; + display: flex; + flex-flow: column; + gap: 8px; + padding: 8px; + background: #2f2f2f; + border-radius: 12px; +} @media screen and (min-width: 425px) and (max-height: 600px) { .MobileApp_main { grid-template-rows: max-content auto max-content max-content 12px max-content auto; @@ -774,6 +909,71 @@ html[data-focus-mode=on] .MobileApp_embed { justify-content: end; } } +@media screen and (max-width: 599px) { + .MobileApp_main[data-youtube-error=true] { + --bg-color: transparent; + --inner-padding: 4px; + grid-template-areas: "logo" "gap3" "embed" "gap4" "switch" "buttons"; + grid-template-rows: max-content 16px auto 12px max-content max-content; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + background: #2f2f2f; + border-radius: var(--outer-radius); + padding: 4px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_switch { + background: #2f2f2f; + padding: 8px 8px 0 8px; + height: 60px; + max-height: 60px; + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + background: #2f2f2f; + padding: 8px; + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_buttons { + border-radius: var(--outer-radius); + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_switch { + background: transparent; + max-height: 0; + } +} +@media screen and (max-width: 599px) and (max-height: 599px) { + .MobileApp_main[data-youtube-error=true] { + max-width: unset; + grid-template-rows: 0 0 auto 12px 0 max-content; + } + .MobileApp_main[data-youtube-error=true] :is(.MobileApp_logo, .MobileApp_switch) { + display: none; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + } +} +@media screen and (min-width: 600px) and (max-height: 450px) { + .MobileApp_main[data-youtube-error=true] { + grid-template-areas: "embed" "buttons" "gap5"; + grid-template-rows: auto max-content 8px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + display: block; + } +} +@media screen and (max-height: 320px) { + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + overflow-y: auto; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + bottom: 0; + position: sticky; + } +} /* pages/duckplayer/app/components/MobileButtons.module.css */ .MobileButtons_buttons { diff --git a/build/android/pages/duckplayer/dist/index.js b/build/android/pages/duckplayer/dist/index.js index ac97968e3..f6486c5da 100644 --- a/build/android/pages/duckplayer/dist/index.js +++ b/build/android/pages/duckplayer/dist/index.js @@ -1982,12 +1982,33 @@ title: "ERROR: Invalid video id", note: "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + blockedVideoErrorHeading: { + title: "YouTube won\u2019t let Duck Player load this video", + note: "Message shown when YouTube has blocked playback of a video" + }, + blockedVideoErrorMessage1: { + title: "YouTube doesn\u2019t allow this video to be viewed outside of YouTube.", + note: "Explanation on why the error is happening." + }, + blockedVideoErrorMessage2: { + title: "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "A message explaining that the blocked video can be watched directly on YouTube." + }, + signInRequiredErrorMessage1: { + title: "YouTube is blocking this video from loading. If you\u2019re using a VPN, try turning it off and reloading this page.", + note: "Explanation on why the error is happening and a suggestions on how to solve it." + }, + signInRequiredErrorMessage2: { + title: "If this doesn\u2019t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "More troubleshooting tips for this specific error" + }, tooltipInfo: { title: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } }; // pages/duckplayer/app/settings.js + var DEFAULT_SIGN_IN_REQURED_HREF = '[href*="//support.google.com/youtube/answer/3037019"]'; var Settings = class _Settings { /** * @param {object} params @@ -1995,17 +2016,20 @@ * @param {{state: 'enabled' | 'disabled'}} [params.pip] * @param {{state: 'enabled' | 'disabled'}} [params.autoplay] * @param {{state: 'enabled' | 'disabled'}} [params.focusMode] + * @param {import("../types/duckplayer.js").InitialSetupResponse['settings']['customError']} [params.customError] */ constructor({ platform = { name: "macos" }, pip = { state: "disabled" }, autoplay = { state: "enabled" }, - focusMode = { state: "enabled" } + focusMode = { state: "enabled" }, + customError = { state: "disabled", signInRequiredSelector: "" } }) { this.platform = platform; this.pip = pip; this.autoplay = autoplay; this.focusMode = focusMode; + this.customError = customError; } /** * @param {keyof import("../types/duckplayer.js").DuckPlayerPageSettings} named @@ -2014,7 +2038,7 @@ */ withFeatureState(named, settings) { if (!settings) return this; - const valid = ["pip", "autoplay", "focusMode"]; + const valid = ["pip", "autoplay", "focusMode", "customError"]; if (!valid.includes(named)) { console.warn(`Excluding invalid feature key ${named}`); return this; @@ -2053,6 +2077,28 @@ } return this; } + /** + * @param {string|null|undefined} newState + * @return {Settings} + */ + withCustomError(newState) { + if (newState === "disabled") { + return new _Settings({ + ...this, + customError: { state: "disabled" } + }); + } + if (newState === "enabled") { + return new _Settings({ + ...this, + customError: { + state: "enabled", + signOnRequiredSelector: DEFAULT_SIGN_IN_REQURED_HREF + } + }); + } + return this; + } /** * @return {string} */ @@ -2959,6 +3005,162 @@ } }; + // pages/duckplayer/app/providers/YouTubeErrorProvider.jsx + var YOUTUBE_ERROR_EVENT = "ddg-duckplayer-youtube-error"; + var YOUTUBE_ERRORS = { + ageRestricted: "age-restricted", + signInRequired: "sign-in-required", + noEmbed: "no-embed", + unknown: "unknown" + }; + var YOUTUBE_ERROR_IDS = Object.values(YOUTUBE_ERRORS); + var YouTubeErrorContext = J({ + /** @type {YouTubeError|null} */ + error: null + }); + function YouTubeErrorProvider({ initial = null, children }) { + let initialError = null; + if (initial && YOUTUBE_ERROR_IDS.includes(initial)) { + initialError = initial; + } + const [error, setError] = h2(initialError); + const messaging2 = useMessaging(); + const platformName = usePlatformName(); + const setFocusMode = useSetFocusMode(); + y2(() => { + const errorEventHandler = (event) => { + const eventError = event.detail?.error; + if (YOUTUBE_ERROR_IDS.includes(eventError) || eventError === null) { + if (eventError && eventError !== error) { + setFocusMode("paused"); + if (platformName === "macos" || platformName === "ios") { + messaging2.reportYouTubeError({ error: eventError }); + } + } else { + setFocusMode("enabled"); + } + setError(eventError); + } + }; + window.addEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + return () => window.removeEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + }, []); + return /* @__PURE__ */ g(YouTubeErrorContext.Provider, { value: { error } }, children); + } + function useYouTubeError() { + return x2(YouTubeErrorContext).error; + } + + // pages/duckplayer/app/features/error-detection.js + var ErrorDetection = class { + /** @type {HTMLIFrameElement} */ + iframe; + /** @type {CustomErrorOptions} */ + options; + /** + * @param {CustomErrorOptions} options + */ + constructor(options) { + this.options = options; + } + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + this.iframe = iframe; + if (!this.options || !this.options.signInRequiredSelector) { + console.log("Missing Custom Error options"); + return null; + } + const documentBody = iframe.contentWindow?.document?.body; + if (documentBody) { + if (this.checkForError(documentBody)) { + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + return null; + } + const observer = new MutationObserver(this.handleMutation.bind(this)); + observer.observe(documentBody, { + childList: true, + subtree: true + // Observe all descendants of the body + }); + return () => { + observer.disconnect(); + }; + } + return null; + } + /** + * Mutation handler that checks new nodes for error states + * + * @type {MutationCallback} + */ + handleMutation(mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (this.checkForError(node)) { + console.log("A node with an error has been added to the document:", node); + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + } + }); + } + } + } + /** + * Attempts to detect the type of error in the YouTube embed iframe + * @returns {YouTubeError} + */ + getErrorType() { + const iframeWindow = ( + /** @type {Window & { ytcfg: object }} */ + this.iframe.contentWindow + ); + let playerResponse; + try { + playerResponse = JSON.parse(iframeWindow.ytcfg?.get("PLAYER_VARS")?.embedded_player_response); + } catch (e3) { + console.log("Could not parse player response", e3); + } + if (typeof playerResponse === "object") { + const { + previewPlayabilityStatus: { desktopLegacyAgeGateReason, status } + } = playerResponse; + if (status === "UNPLAYABLE") { + if (desktopLegacyAgeGateReason === 1) { + return YOUTUBE_ERRORS.ageRestricted; + } + return YOUTUBE_ERRORS.noEmbed; + } + try { + if (this.options?.signInRequiredSelector && !!iframeWindow.document.querySelector(this.options.signInRequiredSelector)) { + return YOUTUBE_ERRORS.signInRequired; + } + } catch (e3) { + console.log("Sign-in required query failed", e3); + } + } + return YOUTUBE_ERRORS.unknown; + } + /** + * Analyses a node and its children to determine if it contains an error state + * + * @param {Node} [node] + */ + checkForError(node) { + if (node?.nodeType === Node.ELEMENT_NODE) { + const element = ( + /** @type {HTMLElement} */ + node + ); + return element.classList.contains("ytp-error") || !!element.querySelector("ytp-error"); + } + return false; + } + }; + // pages/duckplayer/app/features/iframe.js var IframeFeature = class { /** @@ -3015,6 +3217,12 @@ */ mouseCapture: () => { return new MouseCapture(); + }, + /** + * @return {IframeFeature} + */ + errorDetection: () => { + return new ErrorDetection(settings.customError); } }; } @@ -3083,7 +3291,8 @@ features.pip(), features.clickCapture(), features.titleCapture(), - features.mouseCapture() + features.mouseCapture(), + features.errorDetection() ]; const cleanups = []; const loadHandler = () => { @@ -3110,6 +3319,48 @@ return { ref, didLoad: () => didLoad.current = true }; } + // pages/duckplayer/app/components/YouTubeError.jsx + var import_classnames10 = __toESM(require_classnames(), 1); + + // pages/duckplayer/app/components/YouTubeError.module.css + var YouTubeError_default = { + error: "YouTubeError_error", + desktop: "YouTubeError_desktop", + mobile: "YouTubeError_mobile", + container: "YouTubeError_container", + content: "YouTubeError_content", + icon: "YouTubeError_icon", + heading: "YouTubeError_heading", + messages: "YouTubeError_messages" + }; + + // pages/duckplayer/app/components/YouTubeError.jsx + function useErrorStrings(kind) { + const { t: t3 } = useTypedTranslation(); + switch (kind) { + case "sign-in-required": + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("signInRequiredErrorMessage1"), t3("signInRequiredErrorMessage2")], + variant: "paragraphs" + }; + default: + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("blockedVideoErrorMessage1"), t3("blockedVideoErrorMessage2")], + variant: "paragraphs" + }; + } + } + function YouTubeError({ kind, layout }) { + const { heading, messages, variant } = useErrorStrings(kind); + const classes = (0, import_classnames10.default)(YouTubeError_default.error, { + [YouTubeError_default.desktop]: layout === "desktop", + [YouTubeError_default.mobile]: layout === "mobile" + }); + return /* @__PURE__ */ g("div", { className: classes }, /* @__PURE__ */ g("div", { className: YouTubeError_default.container }, /* @__PURE__ */ g("span", { className: YouTubeError_default.icon }), /* @__PURE__ */ g("div", { className: YouTubeError_default.content }, /* @__PURE__ */ g("h1", { className: YouTubeError_default.heading }, heading), messages && variant === "inline" && /* @__PURE__ */ g("p", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("span", { key: item }, item))), messages && variant === "paragraphs" && /* @__PURE__ */ g("div", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("p", { key: item }, item))), messages && variant === "list" && /* @__PURE__ */ g("ul", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("li", { key: item }, item)))))); + } + // pages/duckplayer/app/components/Components.jsx function Components() { const settings = new Settings({ @@ -3118,11 +3369,11 @@ let embed = EmbedSettings.fromHref("https://localhost?videoID=123"); let url = embed?.toEmbedUrl(); if (!url) throw new Error("unreachable"); - return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); + return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "sign-in-required" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "no-embed" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "sign-in-required" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "no-embed" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); } // pages/duckplayer/app/components/MobileApp.jsx - var import_classnames10 = __toESM(require_classnames(), 1); + var import_classnames11 = __toESM(require_classnames(), 1); // pages/duckplayer/app/components/MobileApp.module.css var MobileApp_default = { @@ -3133,7 +3384,8 @@ switch: "MobileApp_switch", embed: "MobileApp_embed", logo: "MobileApp_logo", - buttons: "MobileApp_buttons" + buttons: "MobileApp_buttons", + detachedControls: "MobileApp_detachedControls" }; // pages/duckplayer/app/features/app.js @@ -3231,11 +3483,13 @@ function MobileApp({ embed }) { const settings = useSettings(); const telemetry2 = useTelemetry(); + const youtubeError = useYouTubeError(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g( + return /* @__PURE__ */ g(k, null, !youtubeError && features.focusMode(), /* @__PURE__ */ g( OrientationProvider, { onChange: (orientation) => { + if (youtubeError) return; if (orientation === "portrait") { return FocusMode.enable(); } @@ -3251,7 +3505,10 @@ } function MobileLayout({ embed }) { const platformName = usePlatformName(); - return /* @__PURE__ */ g("main", { class: MobileApp_default.main }, /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("main", { class: MobileApp_default.main, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); } // pages/duckplayer/app/components/DesktopApp.module.css @@ -3272,10 +3529,14 @@ function DesktopApp({ embed }) { const settings = useSettings(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app }, /* @__PURE__ */ g(DesktopLayout, { embed }))); + const youtubeError = useYouTubeError(); + return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g(DesktopLayout, { embed }))); } function DesktopLayout({ embed }) { - return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); } // pages/duckplayer/app/index.js @@ -3291,7 +3552,11 @@ console.log("locale:", environment.locale); document.body.dataset.display = environment.display; const strings = environment.locale === "en" ? duckplayer_default : await getTranslationsFromStringOrLoadDynamically(init2.localeStrings, environment.locale) || duckplayer_default; - const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")); + const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withFeatureState("customError", init2.settings.customError).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")).withCustomError(baseEnvironment2.urlParams.get("customError")); + const initialYouTubeError = ( + /** @type {YouTubeError} */ + baseEnvironment2.urlParams.get("youtubeError") + ); console.log(settings); const embed = createEmbedSettings(window.location.href, settings); const didCatch = (error) => { @@ -3303,7 +3568,7 @@ if (!root) throw new Error("could not render, root element missing"); if (environment.display === "app") { D( - /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( + /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(YouTubeErrorProvider, { initial: initialYouTubeError }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( TranslationProvider, { translationObject: duckplayer_default, @@ -3319,7 +3584,7 @@ textLength: environment.textLength }, /* @__PURE__ */ g(MobileApp, { embed }) - ), /* @__PURE__ */ g(WillThrow, null))))))), + ), /* @__PURE__ */ g(WillThrow, null)))))))), root ); } else if (environment.display === "components") { @@ -3467,6 +3732,13 @@ onUserValuesChanged(cb) { return this.messaging.subscribe("onUserValuesChanged", cb); } + /** + * This will be sent if the application fails to load. + * @param {{error: import('../types/duckplayer.ts').YouTubeError}} params + */ + reportYouTubeError(params) { + this.messaging.notify("reportYouTubeError", params); + } /** * This will be sent if the application has loaded, but a client-side error * has occurred that cannot be recovered from diff --git a/build/android/pages/duckplayer/locales/bg/duckplayer.json b/build/android/pages/duckplayer/locales/bg/duckplayer.json index d7f3d20ef..c807a2f78 100644 --- a/build/android/pages/duckplayer/locales/bg/duckplayer.json +++ b/build/android/pages/duckplayer/locales/bg/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ГРЕШКА: невалиден идентификатор на видеоклипа", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube няма да позволи на Duck Player да зареди това видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволява това видео да бъде гледано извън YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Все пак можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокира зареждането на това видео. Ако използвате VPN, опитайте да го изключите и презаредете тази страница.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ако това не свърши работа, можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player осигурява чисто изживяване без персонализирани реклами в YouTube и предотвратява влиянието на вече гледаните видеоклипове върху препоръките на YouTube." } diff --git a/build/android/pages/duckplayer/locales/cs/duckplayer.json b/build/android/pages/duckplayer/locales/cs/duckplayer.json index 8d24c5726..690569a5d 100644 --- a/build/android/pages/duckplayer/locales/cs/duckplayer.json +++ b/build/android/pages/duckplayer/locales/cs/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatné ID videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovoluje přehrávači Duck Player načíst tohle video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nedovoluje spuštění videa mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Na tohle video se můžeš pořád podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítání tohohle videa. Pokud používáš VPN, zkus ji vypnout a stránku znovu načíst.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Pokud to nefunguje, můžeš se na video podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Přehrávač Duck Player nabízí sledování v minimalistickém prostředí bez personalizovaných reklam a brání tomu, aby sledovaná videa ovlivňovala tvoje doporučení na YouTube." } diff --git a/build/android/pages/duckplayer/locales/da/duckplayer.json b/build/android/pages/duckplayer/locales/da/duckplayer.json index f99ab298e..aae6004c2 100644 --- a/build/android/pages/duckplayer/locales/da/duckplayer.json +++ b/build/android/pages/duckplayer/locales/da/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEJL: Ugyldigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube vil ikke lade Duck Player indlæse denne video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillader ikke, at denne video vises uden for YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokerer for, at denne video kan indlæses. Hvis du bruger en VPN, så prøv at slå den fra og genindlæse denne side.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis dette ikke virker, kan du stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player giver en ren seeroplevelse uden målrettede annoncer og forhindrer, at visningsaktivitet påvirker dine YouTube-anbefalinger." } diff --git a/build/android/pages/duckplayer/locales/de/duckplayer.json b/build/android/pages/duckplayer/locales/de/duckplayer.json index 0ddca103a..abd163119 100644 --- a/build/android/pages/duckplayer/locales/de/duckplayer.json +++ b/build/android/pages/duckplayer/locales/de/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEHLER: Ungültige Video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lässt den Duck Player dieses Video nicht laden", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube erlaubt nicht, dass dieses Video außerhalb von YouTube angesehen wird.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kannst dieses Video auf YouTube ansehen, aber ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockiert das Laden dieses Videos. Falls du ein VPN benutzt, deaktiviere es und lade diese Seite neu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Falls das nicht funktioniert, kannst du das Video dennoch auf YouTube ansehen, jedoch ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." } diff --git a/build/android/pages/duckplayer/locales/el/duckplayer.json b/build/android/pages/duckplayer/locales/el/duckplayer.json index d00195f5e..2d8fa43c5 100644 --- a/build/android/pages/duckplayer/locales/el/duckplayer.json +++ b/build/android/pages/duckplayer/locales/el/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ΣΦΑΛΜΑ: Μη έγκυρο αναγνωριστικό βίντεο", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "Το YouTube δεν θα αφήσει το Duck Player να φορτώσει το βίντεο αυτό", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Το YouTube δεν επιτρέπει την προβολή αυτού του βίντεο εκτός YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Μπορείτε ακόμα να παρακολουθήσετε αυτό το βίντεο στο YouTube, αλλά χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "Το YouTube μπλοκάρει τη φόρτωση αυτού του βίντεο. Εάν χρησιμοποιείτε VPN, δοκιμάστε να το απενεργοποιήσετε και να φορτώσετε εκ νέου αυτήν τη σελίδα.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Εάν δεν λειτουργήσει αυτό, μπορείτε να παρακολουθήσετε αυτό το βίντεο στο YouTube, ωστόσο χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Το Duck Player παρέχει μια καθαρή εμπειρία προβολής χωρίς εξατομικευμένες διαφημίσεις, ενώ εμποδίζει τη δραστηριότητα προβολής να επηρεάσει τις συστάσεις που θα λαμβάνετε στο YouTube." } diff --git a/build/android/pages/duckplayer/locales/en/duckplayer.json b/build/android/pages/duckplayer/locales/en/duckplayer.json index c2b5683b9..fe94917c4 100644 --- a/build/android/pages/duckplayer/locales/en/duckplayer.json +++ b/build/android/pages/duckplayer/locales/en/duckplayer.json @@ -33,6 +33,26 @@ "title": "ERROR: Invalid video id", "note": "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading": { + "title": "YouTube won’t let Duck Player load this video", + "note": "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1": { + "title": "YouTube doesn’t allow this video to be viewed outside of YouTube.", + "note": "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2": { + "title": "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1": { + "title": "YouTube is blocking this video from loading. If you’re using a VPN, try turning it off and reloading this page.", + "note": "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2": { + "title": "If this doesn’t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "More troubleshooting tips for this specific error" + }, "tooltipInfo": { "title": "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } diff --git a/build/android/pages/duckplayer/locales/es/duckplayer.json b/build/android/pages/duckplayer/locales/es/duckplayer.json index 1b5d8b958..f5af0d046 100644 --- a/build/android/pages/duckplayer/locales/es/duckplayer.json +++ b/build/android/pages/duckplayer/locales/es/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERROR: ID de vídeo no válida", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube no permite que Duck Player cargue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube no permite que este vídeo se vea fuera de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional que ofrece Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube está bloqueando la carga de este vídeo. Si estás usando una VPN, intenta desactivarla y volver a cargar la página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si esto no funciona, sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." } diff --git a/build/android/pages/duckplayer/locales/et/duckplayer.json b/build/android/pages/duckplayer/locales/et/duckplayer.json index c9863f4f5..70b30efee 100644 --- a/build/android/pages/duckplayer/locales/et/duckplayer.json +++ b/build/android/pages/duckplayer/locales/et/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIGA: vale video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei luba Duck Playeril seda videot laadida", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei luba seda videot väljaspool YouTube'i vaadata.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Saate seda videot endiselt YouTube'is vaadata, kuid ilma Duck Player'i lisatud privaatsuseta.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokeerib selle video laadimise. Kui kasutate VPN-i, proovige see välja lülitada ning leht uuesti laadida.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Kui see ei aita, saate seda videot ikkagi YouTube'is vaadata, kuid ilma Duck Playeri lisatud privaatsuseta.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pakub isikupärastatud reklaamidest vaba vaatamiskogemust ja takistab, et vaatamisaktiivsus mõjutaks sinu YouTube'i soovitusi." } diff --git a/build/android/pages/duckplayer/locales/fi/duckplayer.json b/build/android/pages/duckplayer/locales/fi/duckplayer.json index e73022b8f..4c830a739 100644 --- a/build/android/pages/duckplayer/locales/fi/duckplayer.json +++ b/build/android/pages/duckplayer/locales/fi/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIRHE: virheellinen videotunnus", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei salli Duck Playerin ladata tätä videota.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei salli tämän videon katsomista YouTuben ulkopuolella.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Voit yhä katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube estää tämän videon latautumisen. Jos käytät VPN:ää, kytke se pois päältä ja lataa tämä sivu uudelleen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jos tämä ei toimi, voit silti katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tarjoaa puhtaan katselukokemuksen ilman kohdennettuja mainoksia ja estää katseluhistoriaa vaikuttamasta YouTube-suosituksiisi." } diff --git a/build/android/pages/duckplayer/locales/fr/duckplayer.json b/build/android/pages/duckplayer/locales/fr/duckplayer.json index 716f0c071..12eb0ac92 100644 --- a/build/android/pages/duckplayer/locales/fr/duckplayer.json +++ b/build/android/pages/duckplayer/locales/fr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERREUR : identifiant vidéo non valide", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne permet pas à Duck Player de charger cette vidéo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube n'autorise pas le visionnage de cette vidéo en dehors de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloque le chargement de cette vidéo. Si vous utilisez un VPN, essayez de le désactiver et de recharger cette page.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si cela ne fonctionne pas, vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." } diff --git a/build/android/pages/duckplayer/locales/hr/duckplayer.json b/build/android/pages/duckplayer/locales/hr/duckplayer.json index 3f0e8aeae..48fdbf997 100644 --- a/build/android/pages/duckplayer/locales/hr/duckplayer.json +++ b/build/android/pages/duckplayer/locales/hr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "POGREŠKA: Nevažeći ID videozapisa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dopušta Duck Playeru da učita ovaj videozapis.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dopušta da se ovaj videozapis gleda izvan YouTubea.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Još uvijek možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokira učitavanje ovog videozapisa. Ako koristiš VPN, pokušaj ga isključiti i ponovno učitati ovu stranicu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ako to ne uspije, i dalje možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pruža čisti doživljaj gledanja bez personaliziranih oglasa i sprječava da aktivnosti gledanja utječu na tvoje preporuke na YouTubeu." } diff --git a/build/android/pages/duckplayer/locales/hu/duckplayer.json b/build/android/pages/duckplayer/locales/hu/duckplayer.json index 3bbe06210..c8faf475f 100644 --- a/build/android/pages/duckplayer/locales/hu/duckplayer.json +++ b/build/android/pages/duckplayer/locales/hu/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HIBA: Érvénytelen videoazonosító", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "A YouTube nem engedi, hogy a Duck Player betöltse ezt a videót", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "A YouTube nem engedi, hogy ezt a videót a YouTube-on kívül nézd meg.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Megnézheted a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "A YouTube blokkolja ennek a videónak a betöltését. Ha VPN-t használsz, próbáld meg, hogy kikapcsolod, majd újra betöltöd ezt az oldalt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ha ez nem működik, akkor is megnézheted ezt a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "A Duck Player személyre szabott hirdetések nélküli, letisztult megtekintési élményt nyújt, és megakadályozza, hogy a megtekintési tevékenységed befolyásolja a neked szóló YouTube-ajánlásokat." } diff --git a/build/android/pages/duckplayer/locales/it/duckplayer.json b/build/android/pages/duckplayer/locales/it/duckplayer.json index 9ce216677..464303fca 100644 --- a/build/android/pages/duckplayer/locales/it/duckplayer.json +++ b/build/android/pages/duckplayer/locales/it/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRORE: ID video non valido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube non consente a Duck Player di caricare questo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Questo video si può vedere solo su YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Puoi ancora guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube sta impedendo il caricamento di questo video. Se stai utilizzando una VPN, prova a disattivarla e a ricaricare questa pagina.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se il problema non si risolve, puoi comunque guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." } diff --git a/build/android/pages/duckplayer/locales/lt/duckplayer.json b/build/android/pages/duckplayer/locales/lt/duckplayer.json index 1b282dd2c..c8dd25e38 100644 --- a/build/android/pages/duckplayer/locales/lt/duckplayer.json +++ b/build/android/pages/duckplayer/locales/lt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KLAIDA: netinkamas vaizdo įrašo ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "„YouTube“ neleidžia „Duck Player“ įkelti šio vaizdo įrašo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "„YouTube“ neleidžia šio vaizdo įrašo žiūrėti ne „YouTube“ platformoje.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šį vaizdo įrašą vis dar gali žiūrėti „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "„YouTube“ blokuoja šio vaizdo įrašo įkėlimą. Jei naudoji VPN, pabandyk jį išjungti ir iš naujo įkelti šį puslapį.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jei tai neveikia, vis tiek gali žiūrėti šį vaizdo įrašą „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "„Duck Player“ užtikrina nepriekaištingą žiūrėjimo patirtį be suasmenintų reklamų ir neleidžia žiūrėjimo veiklai daryti įtakos „YouTube“ rekomendacijoms." } diff --git a/build/android/pages/duckplayer/locales/lv/duckplayer.json b/build/android/pages/duckplayer/locales/lv/duckplayer.json index 46d0bee8e..3ae376c31 100644 --- a/build/android/pages/duckplayer/locales/lv/duckplayer.json +++ b/build/android/pages/duckplayer/locales/lv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KĻŪDA: Nederīgs video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube neļauj Duck Player ielādēt šo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube neļauj skatīties šo video ārpus YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šo videoklipu joprojām vari skatīties vietnē YouTube, taču bez papildu Duck Player konfidencialitātes.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloķē šī video ielādi. Ja tu izmanto VPN, mēģini to izslēgt un pārlādēt šo lapu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ja tas nedarbojas, joprojām vari skatīties šo video vietnē YouTube, taču bez papildu privātuma, ko nodrošina Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player nodrošina netraucētu skatīšanās pieredzi bez personalizētām reklāmām un neļauj skatīšanās darbībām ietekmēt tavus YouTube ieteikumus." } diff --git a/build/android/pages/duckplayer/locales/nb/duckplayer.json b/build/android/pages/duckplayer/locales/nb/duckplayer.json index 4c1d826f6..fef475447 100644 --- a/build/android/pages/duckplayer/locales/nb/duckplayer.json +++ b/build/android/pages/duckplayer/locales/nb/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEIL: Ugyldig video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lar ikke Duck Player laste denne videoen", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillater ikke visning av denne videoen utenfor YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fremdeles se videoen på YouTube, men uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkerer denne videoen fra å lastes. Hvis du bruker en VPN, kan du prøve å slå den av og laste denne siden på nytt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis ikke det virker, kan du fremdeles se videoen på YouTube, bare uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tilbyr en ren seeropplevelse uten tilpassede annonser og forhindrer at seeraktiviteten din påvirker YouTube-anbefalingene dine." } diff --git a/build/android/pages/duckplayer/locales/nl/duckplayer.json b/build/android/pages/duckplayer/locales/nl/duckplayer.json index a1be8669e..51a0ece78 100644 --- a/build/android/pages/duckplayer/locales/nl/duckplayer.json +++ b/build/android/pages/duckplayer/locales/nl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FOUT: ongeldige video-id", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube staat Duck Player niet toe om deze video te laden.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube staat niet toe dat je deze video buiten YouTube bekijkt.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Je kunt deze video nog steeds bekijken op YouTube, maar zonder de extra privacy van Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkeert het laden van deze video. Als je een VPN gebruikt, probeer deze dan uit te schakelen en deze pagina opnieuw te laden.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Als dit niet werkt, kun je deze video nog steeds op YouTube bekijken, maar dan zonder de extra privacy van Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." } diff --git a/build/android/pages/duckplayer/locales/pl/duckplayer.json b/build/android/pages/duckplayer/locales/pl/duckplayer.json index accd3fde5..8b633d415 100644 --- a/build/android/pages/duckplayer/locales/pl/duckplayer.json +++ b/build/android/pages/duckplayer/locales/pl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "BŁĄD: nieprawidłowy identyfikator filmu", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nie zezwala na załadowanie tego filmu przez Duck Player", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nie zezwala na oglądanie tego filmu poza YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Nadal możesz oglądać ten film na YouTube, ale bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje ładowanie tego filmu. Jeśli korzystasz z sieci VPN, spróbuj ją wyłączyć i ponownie załadować tę stronę.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jeśli to nie pomoże, nadal możesz oglądać ten film na YouTube, jednak bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." } diff --git a/build/android/pages/duckplayer/locales/pt/duckplayer.json b/build/android/pages/duckplayer/locales/pt/duckplayer.json index a5bfca188..2ff6fad9c 100644 --- a/build/android/pages/duckplayer/locales/pt/duckplayer.json +++ b/build/android/pages/duckplayer/locales/pt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRO: ID de vídeo inválido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "O YouTube não permite que o Duck Player carregue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "O YouTube não permite que vejas este vídeo fora do YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "O YouTube está a bloquear o carregamento deste vídeo. Se estiveres a usar uma VPN, tenta desativá-la e recarregar esta página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se isto não funcionar, continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." } diff --git a/build/android/pages/duckplayer/locales/ro/duckplayer.json b/build/android/pages/duckplayer/locales/ro/duckplayer.json index bfafec70e..25ffac535 100644 --- a/build/android/pages/duckplayer/locales/ro/duckplayer.json +++ b/build/android/pages/duckplayer/locales/ro/duckplayer.json @@ -32,6 +32,26 @@ "title" : "EROARE: ID video incorect", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nu permite Duck Player să încarce acest videoclip", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nu permite ca acest videoclip să fie vizionat în afara YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blochează încărcarea acestui videoclip. Dacă folosești un VPN, încearcă să-l dezactivezi și să reîncarci această pagină.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Dacă acest lucru nu funcționează, poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player oferă o experiență de vizionare fără perturbări, fără reclame personalizate și împiedică activitatea de vizionare să îți influențeze recomandările YouTube." } diff --git a/build/android/pages/duckplayer/locales/ru/duckplayer.json b/build/android/pages/duckplayer/locales/ru/duckplayer.json index 4bf5cc0c1..d1a3fc528 100644 --- a/build/android/pages/duckplayer/locales/ru/duckplayer.json +++ b/build/android/pages/duckplayer/locales/ru/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ОШИБКА: Неверный идентификатор видео", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube не позволяет проигрывателю Duck Player загрузить это видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволяет смотреть это видео вне своей платформы.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Вы по-прежнему можете посмотреть этот ролик на YouTube, но уже без дополнительной защиты Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокирует загрузку этого видео. Если вы используете VPN, отключите ее и перезагрузите страницу.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Если это не даст результата, вы все равно сможете просмотреть это видео на YouTube, но без дополнительной защиты конфиденциальности, обеспечиваемой Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." } diff --git a/build/android/pages/duckplayer/locales/sk/duckplayer.json b/build/android/pages/duckplayer/locales/sk/duckplayer.json index 88a2cc3a5..a68cdeb89 100644 --- a/build/android/pages/duckplayer/locales/sk/duckplayer.json +++ b/build/android/pages/duckplayer/locales/sk/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatný identifikátor videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovolí Duck Playeru načítať toto video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nepovoľuje, aby sa toto video pozeralo mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Toto video si môžeš pozrieť aj na YouTube, ale bez dodatočného súkromia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítanie tohto videa. Ak používaš sieť VPN, skús ju vypnúť a znova načítať túto stránku.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ak to nefunguje, môžeš si toto video pozrieť aj na YouTube, ale bez dodatočnej ochrany súkromia, ktorú poskytuje Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player poskytuje čisté zobrazenie bez personalizovaných reklám a zabraňuje tomu, aby aktivita pri sledovaní ovplyvňovala vaše odporúčania v službe YouTube." } diff --git a/build/android/pages/duckplayer/locales/sl/duckplayer.json b/build/android/pages/duckplayer/locales/sl/duckplayer.json index 7d4a89155..977830ef4 100644 --- a/build/android/pages/duckplayer/locales/sl/duckplayer.json +++ b/build/android/pages/duckplayer/locales/sl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "NAPAKA: Neveljaven ID videoposnetka", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dovoli predvajalniku Duck Player naložiti tega videa", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dovoljuje, da si ta videoposnetek ogledate zunaj YouTuba.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Ta videoposnetek si lahko še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube preprečuje nalaganje tega videoposnetka. Če uporabljate omrežje VPN, ga poskusite izklopiti in znova naložite to stran.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Če to ne deluje, si lahko ta videoposnetek še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Predvajalnik Duck Player zagotavlja čisto izkušnjo gledanja brez prilagojenih oglasov in preprečuje, da bi dejavnost gledanja vplivala na vaša priporočila v YouTubu." } diff --git a/build/android/pages/duckplayer/locales/sv/duckplayer.json b/build/android/pages/duckplayer/locales/sv/duckplayer.json index cb5b75e4c..444e91750 100644 --- a/build/android/pages/duckplayer/locales/sv/duckplayer.json +++ b/build/android/pages/duckplayer/locales/sv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEL: Ogiltigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube låter inte Duck Player läsa in den här videon", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillåter inte att den här videon ses utanför YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockerar den här videon från att läsas in. Om du använder ett VPN kan du prova stänga av det och läsa in sidan igen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Om det inte fungerar kan du fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ger en störningsfri visningsupplevelse utan personliga annonser och förhindrar att din tittaraktivitet påverkar YouTube-rekommendationer." } diff --git a/build/android/pages/duckplayer/locales/tr/duckplayer.json b/build/android/pages/duckplayer/locales/tr/duckplayer.json index c23cab2bb..e733fcaa6 100644 --- a/build/android/pages/duckplayer/locales/tr/duckplayer.json +++ b/build/android/pages/duckplayer/locales/tr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HATA: Geçersiz video kimliği", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube, Duck Player'ın bu videoyu yüklemesine izin vermiyor", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube, bu videonun YouTube dışında izlenmesine izin vermiyor.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Bu videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bu videonun yüklenmesini engelliyor. VPN kullanıyorsanız, VPN'i kapatıp bu sayfayı yeniden yüklemeyi deneyin.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Bu işe yaramazsa, videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player, kişiselleştirilmiş reklamlar olmadan temiz bir görüntüleme deneyimi sağlar ve görüntüleme etkinliğinin YouTube önerilerinizi etkilemesini önler." } diff --git a/build/integration/pages/duckplayer/dist/index.css b/build/integration/pages/duckplayer/dist/index.css index cff98b790..7e8fbf3a0 100644 --- a/build/integration/pages/duckplayer/dist/index.css +++ b/build/integration/pages/duckplayer/dist/index.css @@ -61,6 +61,7 @@ body[data-display=app] { /* pages/duckplayer/app/components/Components.module.css */ .Components_main { + background-color: #000; color: white; max-width: 3840px; margin: 0 auto; @@ -112,7 +113,7 @@ body[data-display=app] { text-decoration: none; } [data-layout=mobile] .Button_button { - background-color: #2f2f2f; + background-color: rgba(255, 255, 255, 0.12); } .Button_button:hover, .Button_button:focus-visible { @@ -188,7 +189,7 @@ body[data-display=app] { .SwitchBarMobile_switchBar { display: grid; border-radius: 8px; - background: #2f2f2f; + background: rgba(255, 255, 255, 0.12); padding-inline: 16px; height: 100%; line-height: 1.1; @@ -518,6 +519,9 @@ body[data-display=app] { .Wordmark_mobile_logo { height: 100px; } + [data-youtube-error=true] .Wordmark_mobile_logo { + height: 44px; + } } .Wordmark_mobile_logoSvg img { display: block; @@ -589,6 +593,127 @@ body[data-display=app] { } } +/* pages/duckplayer/app/components/YouTubeError.module.css */ +.YouTubeError_error { + align-items: center; + background: rgba(0, 0, 0, 0.6); + display: grid; + height: 100%; + justify-items: center; +} +.YouTubeError_error.YouTubeError_desktop { + height: var(--frame-height); + overflow: hidden; + position: relative; + z-index: 1; +} +.YouTubeError_error.YouTubeError_mobile { + border-radius: var(--inner-radius); + height: 100%; + overflow: auto; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_error.YouTubeError_mobile { + aspect-ratio: 16 / 9; + } +} +.YouTubeError_desktop { + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.YouTubeError_container { + column-gap: 24px; + display: flex; + flex-flow: row; + margin: 0; + max-width: 680px; + padding: 0 40px; + row-gap: 4px; +} +.YouTubeError_mobile .YouTubeError_container { + flex-flow: column; + padding: 0 24px; +} +@media screen and (min-height: 320px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 16px 0; + } +} +@media screen and (min-width: 375px) and (min-height: 400px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 36px 0; + } +} +.YouTubeError_content { + display: flex; + flex-direction: column; + gap: 4px; + margin: 16px 0; +} +@media screen and (min-width: 600px) { + .YouTubeError_content { + margin: 24px 0; + } +} +.YouTubeError_icon { + align-self: center; + display: flex; + justify-content: center; +} +.YouTubeError_icon::before { + content: " "; + display: block; + background: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A%0A') no-repeat; + height: 48px; + width: 48px; +} +@media screen and (max-width: 320px) { + .YouTubeError_icon { + display: none; + } +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_icon { + justify-content: start; + } + .YouTubeError_icon::before { + background-image: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A %0A%0A'); + height: 96px; + width: 128px; + } +} +.YouTubeError_heading { + color: #fff; + font-size: 20px; + font-weight: 700; + line-height: calc(24 / 20); + margin: 0; +} +.YouTubeError_messages { + color: #ccc; + font-size: 16px; + line-height: calc(24 / 16); +} +div.YouTubeError_messages { + display: flex; + flex-direction: column; + gap: 24px; +} +div.YouTubeError_messages p { + margin: 0; +} +p.YouTubeError_messages { + margin: 0; +} +ul.YouTubeError_messages li { + list-style: disc; + margin-left: 24px; +} + /* pages/duckplayer/app/components/MobileApp.module.css */ body[data-display=app] { padding: 8px; @@ -626,6 +751,7 @@ html[data-focus-mode=on] .MobileApp_hideInFocus { --inner-radius: 12px; --logo-width: 157px; --inner-padding: 8px; + --mobile-buttons-padding: 8px; position: relative; max-width: 100vh; margin: 0 auto; @@ -681,6 +807,15 @@ html[data-focus-mode=on] .MobileApp_embed { grid-area: switch; height: 44px; } +.MobileApp_detachedControls { + grid-area: detached; + display: flex; + flex-flow: column; + gap: 8px; + padding: 8px; + background: #2f2f2f; + border-radius: 12px; +} @media screen and (min-width: 425px) and (max-height: 600px) { .MobileApp_main { grid-template-rows: max-content auto max-content max-content 12px max-content auto; @@ -774,6 +909,71 @@ html[data-focus-mode=on] .MobileApp_embed { justify-content: end; } } +@media screen and (max-width: 599px) { + .MobileApp_main[data-youtube-error=true] { + --bg-color: transparent; + --inner-padding: 4px; + grid-template-areas: "logo" "gap3" "embed" "gap4" "switch" "buttons"; + grid-template-rows: max-content 16px auto 12px max-content max-content; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + background: #2f2f2f; + border-radius: var(--outer-radius); + padding: 4px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_switch { + background: #2f2f2f; + padding: 8px 8px 0 8px; + height: 60px; + max-height: 60px; + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + background: #2f2f2f; + padding: 8px; + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_buttons { + border-radius: var(--outer-radius); + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_switch { + background: transparent; + max-height: 0; + } +} +@media screen and (max-width: 599px) and (max-height: 599px) { + .MobileApp_main[data-youtube-error=true] { + max-width: unset; + grid-template-rows: 0 0 auto 12px 0 max-content; + } + .MobileApp_main[data-youtube-error=true] :is(.MobileApp_logo, .MobileApp_switch) { + display: none; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + } +} +@media screen and (min-width: 600px) and (max-height: 450px) { + .MobileApp_main[data-youtube-error=true] { + grid-template-areas: "embed" "buttons" "gap5"; + grid-template-rows: auto max-content 8px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + display: block; + } +} +@media screen and (max-height: 320px) { + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + overflow-y: auto; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + bottom: 0; + position: sticky; + } +} /* pages/duckplayer/app/components/MobileButtons.module.css */ .MobileButtons_buttons { diff --git a/build/integration/pages/duckplayer/dist/index.js b/build/integration/pages/duckplayer/dist/index.js index 44d80fa71..a6e01d2b1 100644 --- a/build/integration/pages/duckplayer/dist/index.js +++ b/build/integration/pages/duckplayer/dist/index.js @@ -1982,12 +1982,33 @@ title: "ERROR: Invalid video id", note: "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + blockedVideoErrorHeading: { + title: "YouTube won\u2019t let Duck Player load this video", + note: "Message shown when YouTube has blocked playback of a video" + }, + blockedVideoErrorMessage1: { + title: "YouTube doesn\u2019t allow this video to be viewed outside of YouTube.", + note: "Explanation on why the error is happening." + }, + blockedVideoErrorMessage2: { + title: "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "A message explaining that the blocked video can be watched directly on YouTube." + }, + signInRequiredErrorMessage1: { + title: "YouTube is blocking this video from loading. If you\u2019re using a VPN, try turning it off and reloading this page.", + note: "Explanation on why the error is happening and a suggestions on how to solve it." + }, + signInRequiredErrorMessage2: { + title: "If this doesn\u2019t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "More troubleshooting tips for this specific error" + }, tooltipInfo: { title: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } }; // pages/duckplayer/app/settings.js + var DEFAULT_SIGN_IN_REQURED_HREF = '[href*="//support.google.com/youtube/answer/3037019"]'; var Settings = class _Settings { /** * @param {object} params @@ -1995,17 +2016,20 @@ * @param {{state: 'enabled' | 'disabled'}} [params.pip] * @param {{state: 'enabled' | 'disabled'}} [params.autoplay] * @param {{state: 'enabled' | 'disabled'}} [params.focusMode] + * @param {import("../types/duckplayer.js").InitialSetupResponse['settings']['customError']} [params.customError] */ constructor({ platform = { name: "macos" }, pip = { state: "disabled" }, autoplay = { state: "enabled" }, - focusMode = { state: "enabled" } + focusMode = { state: "enabled" }, + customError = { state: "disabled", signInRequiredSelector: "" } }) { this.platform = platform; this.pip = pip; this.autoplay = autoplay; this.focusMode = focusMode; + this.customError = customError; } /** * @param {keyof import("../types/duckplayer.js").DuckPlayerPageSettings} named @@ -2014,7 +2038,7 @@ */ withFeatureState(named, settings) { if (!settings) return this; - const valid = ["pip", "autoplay", "focusMode"]; + const valid = ["pip", "autoplay", "focusMode", "customError"]; if (!valid.includes(named)) { console.warn(`Excluding invalid feature key ${named}`); return this; @@ -2053,6 +2077,28 @@ } return this; } + /** + * @param {string|null|undefined} newState + * @return {Settings} + */ + withCustomError(newState) { + if (newState === "disabled") { + return new _Settings({ + ...this, + customError: { state: "disabled" } + }); + } + if (newState === "enabled") { + return new _Settings({ + ...this, + customError: { + state: "enabled", + signOnRequiredSelector: DEFAULT_SIGN_IN_REQURED_HREF + } + }); + } + return this; + } /** * @return {string} */ @@ -2959,6 +3005,162 @@ } }; + // pages/duckplayer/app/providers/YouTubeErrorProvider.jsx + var YOUTUBE_ERROR_EVENT = "ddg-duckplayer-youtube-error"; + var YOUTUBE_ERRORS = { + ageRestricted: "age-restricted", + signInRequired: "sign-in-required", + noEmbed: "no-embed", + unknown: "unknown" + }; + var YOUTUBE_ERROR_IDS = Object.values(YOUTUBE_ERRORS); + var YouTubeErrorContext = J({ + /** @type {YouTubeError|null} */ + error: null + }); + function YouTubeErrorProvider({ initial = null, children }) { + let initialError = null; + if (initial && YOUTUBE_ERROR_IDS.includes(initial)) { + initialError = initial; + } + const [error, setError] = h2(initialError); + const messaging2 = useMessaging(); + const platformName = usePlatformName(); + const setFocusMode = useSetFocusMode(); + y2(() => { + const errorEventHandler = (event) => { + const eventError = event.detail?.error; + if (YOUTUBE_ERROR_IDS.includes(eventError) || eventError === null) { + if (eventError && eventError !== error) { + setFocusMode("paused"); + if (platformName === "macos" || platformName === "ios") { + messaging2.reportYouTubeError({ error: eventError }); + } + } else { + setFocusMode("enabled"); + } + setError(eventError); + } + }; + window.addEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + return () => window.removeEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + }, []); + return /* @__PURE__ */ g(YouTubeErrorContext.Provider, { value: { error } }, children); + } + function useYouTubeError() { + return x2(YouTubeErrorContext).error; + } + + // pages/duckplayer/app/features/error-detection.js + var ErrorDetection = class { + /** @type {HTMLIFrameElement} */ + iframe; + /** @type {CustomErrorOptions} */ + options; + /** + * @param {CustomErrorOptions} options + */ + constructor(options) { + this.options = options; + } + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + this.iframe = iframe; + if (!this.options || !this.options.signInRequiredSelector) { + console.log("Missing Custom Error options"); + return null; + } + const documentBody = iframe.contentWindow?.document?.body; + if (documentBody) { + if (this.checkForError(documentBody)) { + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + return null; + } + const observer = new MutationObserver(this.handleMutation.bind(this)); + observer.observe(documentBody, { + childList: true, + subtree: true + // Observe all descendants of the body + }); + return () => { + observer.disconnect(); + }; + } + return null; + } + /** + * Mutation handler that checks new nodes for error states + * + * @type {MutationCallback} + */ + handleMutation(mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (this.checkForError(node)) { + console.log("A node with an error has been added to the document:", node); + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + } + }); + } + } + } + /** + * Attempts to detect the type of error in the YouTube embed iframe + * @returns {YouTubeError} + */ + getErrorType() { + const iframeWindow = ( + /** @type {Window & { ytcfg: object }} */ + this.iframe.contentWindow + ); + let playerResponse; + try { + playerResponse = JSON.parse(iframeWindow.ytcfg?.get("PLAYER_VARS")?.embedded_player_response); + } catch (e3) { + console.log("Could not parse player response", e3); + } + if (typeof playerResponse === "object") { + const { + previewPlayabilityStatus: { desktopLegacyAgeGateReason, status } + } = playerResponse; + if (status === "UNPLAYABLE") { + if (desktopLegacyAgeGateReason === 1) { + return YOUTUBE_ERRORS.ageRestricted; + } + return YOUTUBE_ERRORS.noEmbed; + } + try { + if (this.options?.signInRequiredSelector && !!iframeWindow.document.querySelector(this.options.signInRequiredSelector)) { + return YOUTUBE_ERRORS.signInRequired; + } + } catch (e3) { + console.log("Sign-in required query failed", e3); + } + } + return YOUTUBE_ERRORS.unknown; + } + /** + * Analyses a node and its children to determine if it contains an error state + * + * @param {Node} [node] + */ + checkForError(node) { + if (node?.nodeType === Node.ELEMENT_NODE) { + const element = ( + /** @type {HTMLElement} */ + node + ); + return element.classList.contains("ytp-error") || !!element.querySelector("ytp-error"); + } + return false; + } + }; + // pages/duckplayer/app/features/iframe.js var IframeFeature = class { /** @@ -3015,6 +3217,12 @@ */ mouseCapture: () => { return new MouseCapture(); + }, + /** + * @return {IframeFeature} + */ + errorDetection: () => { + return new ErrorDetection(settings.customError); } }; } @@ -3083,7 +3291,8 @@ features.pip(), features.clickCapture(), features.titleCapture(), - features.mouseCapture() + features.mouseCapture(), + features.errorDetection() ]; const cleanups = []; const loadHandler = () => { @@ -3110,6 +3319,48 @@ return { ref, didLoad: () => didLoad.current = true }; } + // pages/duckplayer/app/components/YouTubeError.jsx + var import_classnames10 = __toESM(require_classnames(), 1); + + // pages/duckplayer/app/components/YouTubeError.module.css + var YouTubeError_default = { + error: "YouTubeError_error", + desktop: "YouTubeError_desktop", + mobile: "YouTubeError_mobile", + container: "YouTubeError_container", + content: "YouTubeError_content", + icon: "YouTubeError_icon", + heading: "YouTubeError_heading", + messages: "YouTubeError_messages" + }; + + // pages/duckplayer/app/components/YouTubeError.jsx + function useErrorStrings(kind) { + const { t: t3 } = useTypedTranslation(); + switch (kind) { + case "sign-in-required": + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("signInRequiredErrorMessage1"), t3("signInRequiredErrorMessage2")], + variant: "paragraphs" + }; + default: + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("blockedVideoErrorMessage1"), t3("blockedVideoErrorMessage2")], + variant: "paragraphs" + }; + } + } + function YouTubeError({ kind, layout }) { + const { heading, messages, variant } = useErrorStrings(kind); + const classes = (0, import_classnames10.default)(YouTubeError_default.error, { + [YouTubeError_default.desktop]: layout === "desktop", + [YouTubeError_default.mobile]: layout === "mobile" + }); + return /* @__PURE__ */ g("div", { className: classes }, /* @__PURE__ */ g("div", { className: YouTubeError_default.container }, /* @__PURE__ */ g("span", { className: YouTubeError_default.icon }), /* @__PURE__ */ g("div", { className: YouTubeError_default.content }, /* @__PURE__ */ g("h1", { className: YouTubeError_default.heading }, heading), messages && variant === "inline" && /* @__PURE__ */ g("p", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("span", { key: item }, item))), messages && variant === "paragraphs" && /* @__PURE__ */ g("div", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("p", { key: item }, item))), messages && variant === "list" && /* @__PURE__ */ g("ul", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("li", { key: item }, item)))))); + } + // pages/duckplayer/app/components/Components.jsx function Components() { const settings = new Settings({ @@ -3118,11 +3369,11 @@ let embed = EmbedSettings.fromHref("https://localhost?videoID=123"); let url = embed?.toEmbedUrl(); if (!url) throw new Error("unreachable"); - return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); + return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "sign-in-required" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "no-embed" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "sign-in-required" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "no-embed" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); } // pages/duckplayer/app/components/MobileApp.jsx - var import_classnames10 = __toESM(require_classnames(), 1); + var import_classnames11 = __toESM(require_classnames(), 1); // pages/duckplayer/app/components/MobileApp.module.css var MobileApp_default = { @@ -3133,7 +3384,8 @@ switch: "MobileApp_switch", embed: "MobileApp_embed", logo: "MobileApp_logo", - buttons: "MobileApp_buttons" + buttons: "MobileApp_buttons", + detachedControls: "MobileApp_detachedControls" }; // pages/duckplayer/app/features/app.js @@ -3231,11 +3483,13 @@ function MobileApp({ embed }) { const settings = useSettings(); const telemetry2 = useTelemetry(); + const youtubeError = useYouTubeError(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g( + return /* @__PURE__ */ g(k, null, !youtubeError && features.focusMode(), /* @__PURE__ */ g( OrientationProvider, { onChange: (orientation) => { + if (youtubeError) return; if (orientation === "portrait") { return FocusMode.enable(); } @@ -3251,7 +3505,10 @@ } function MobileLayout({ embed }) { const platformName = usePlatformName(); - return /* @__PURE__ */ g("main", { class: MobileApp_default.main }, /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("main", { class: MobileApp_default.main, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); } // pages/duckplayer/app/components/DesktopApp.module.css @@ -3272,10 +3529,14 @@ function DesktopApp({ embed }) { const settings = useSettings(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app }, /* @__PURE__ */ g(DesktopLayout, { embed }))); + const youtubeError = useYouTubeError(); + return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g(DesktopLayout, { embed }))); } function DesktopLayout({ embed }) { - return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); } // pages/duckplayer/app/index.js @@ -3291,7 +3552,11 @@ console.log("locale:", environment.locale); document.body.dataset.display = environment.display; const strings = environment.locale === "en" ? duckplayer_default : await getTranslationsFromStringOrLoadDynamically(init2.localeStrings, environment.locale) || duckplayer_default; - const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")); + const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withFeatureState("customError", init2.settings.customError).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")).withCustomError(baseEnvironment2.urlParams.get("customError")); + const initialYouTubeError = ( + /** @type {YouTubeError} */ + baseEnvironment2.urlParams.get("youtubeError") + ); console.log(settings); const embed = createEmbedSettings(window.location.href, settings); const didCatch = (error) => { @@ -3303,7 +3568,7 @@ if (!root) throw new Error("could not render, root element missing"); if (environment.display === "app") { D( - /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( + /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(YouTubeErrorProvider, { initial: initialYouTubeError }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( TranslationProvider, { translationObject: duckplayer_default, @@ -3319,7 +3584,7 @@ textLength: environment.textLength }, /* @__PURE__ */ g(MobileApp, { embed }) - ), /* @__PURE__ */ g(WillThrow, null))))))), + ), /* @__PURE__ */ g(WillThrow, null)))))))), root ); } else if (environment.display === "components") { @@ -3467,6 +3732,13 @@ onUserValuesChanged(cb) { return this.messaging.subscribe("onUserValuesChanged", cb); } + /** + * This will be sent if the application fails to load. + * @param {{error: import('../types/duckplayer.ts').YouTubeError}} params + */ + reportYouTubeError(params) { + this.messaging.notify("reportYouTubeError", params); + } /** * This will be sent if the application has loaded, but a client-side error * has occurred that cannot be recovered from diff --git a/build/integration/pages/duckplayer/locales/bg/duckplayer.json b/build/integration/pages/duckplayer/locales/bg/duckplayer.json index d7f3d20ef..c807a2f78 100644 --- a/build/integration/pages/duckplayer/locales/bg/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/bg/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ГРЕШКА: невалиден идентификатор на видеоклипа", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube няма да позволи на Duck Player да зареди това видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволява това видео да бъде гледано извън YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Все пак можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокира зареждането на това видео. Ако използвате VPN, опитайте да го изключите и презаредете тази страница.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ако това не свърши работа, можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player осигурява чисто изживяване без персонализирани реклами в YouTube и предотвратява влиянието на вече гледаните видеоклипове върху препоръките на YouTube." } diff --git a/build/integration/pages/duckplayer/locales/cs/duckplayer.json b/build/integration/pages/duckplayer/locales/cs/duckplayer.json index 8d24c5726..690569a5d 100644 --- a/build/integration/pages/duckplayer/locales/cs/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/cs/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatné ID videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovoluje přehrávači Duck Player načíst tohle video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nedovoluje spuštění videa mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Na tohle video se můžeš pořád podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítání tohohle videa. Pokud používáš VPN, zkus ji vypnout a stránku znovu načíst.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Pokud to nefunguje, můžeš se na video podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Přehrávač Duck Player nabízí sledování v minimalistickém prostředí bez personalizovaných reklam a brání tomu, aby sledovaná videa ovlivňovala tvoje doporučení na YouTube." } diff --git a/build/integration/pages/duckplayer/locales/da/duckplayer.json b/build/integration/pages/duckplayer/locales/da/duckplayer.json index f99ab298e..aae6004c2 100644 --- a/build/integration/pages/duckplayer/locales/da/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/da/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEJL: Ugyldigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube vil ikke lade Duck Player indlæse denne video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillader ikke, at denne video vises uden for YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokerer for, at denne video kan indlæses. Hvis du bruger en VPN, så prøv at slå den fra og genindlæse denne side.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis dette ikke virker, kan du stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player giver en ren seeroplevelse uden målrettede annoncer og forhindrer, at visningsaktivitet påvirker dine YouTube-anbefalinger." } diff --git a/build/integration/pages/duckplayer/locales/de/duckplayer.json b/build/integration/pages/duckplayer/locales/de/duckplayer.json index 0ddca103a..abd163119 100644 --- a/build/integration/pages/duckplayer/locales/de/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/de/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEHLER: Ungültige Video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lässt den Duck Player dieses Video nicht laden", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube erlaubt nicht, dass dieses Video außerhalb von YouTube angesehen wird.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kannst dieses Video auf YouTube ansehen, aber ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockiert das Laden dieses Videos. Falls du ein VPN benutzt, deaktiviere es und lade diese Seite neu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Falls das nicht funktioniert, kannst du das Video dennoch auf YouTube ansehen, jedoch ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." } diff --git a/build/integration/pages/duckplayer/locales/el/duckplayer.json b/build/integration/pages/duckplayer/locales/el/duckplayer.json index d00195f5e..2d8fa43c5 100644 --- a/build/integration/pages/duckplayer/locales/el/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/el/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ΣΦΑΛΜΑ: Μη έγκυρο αναγνωριστικό βίντεο", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "Το YouTube δεν θα αφήσει το Duck Player να φορτώσει το βίντεο αυτό", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Το YouTube δεν επιτρέπει την προβολή αυτού του βίντεο εκτός YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Μπορείτε ακόμα να παρακολουθήσετε αυτό το βίντεο στο YouTube, αλλά χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "Το YouTube μπλοκάρει τη φόρτωση αυτού του βίντεο. Εάν χρησιμοποιείτε VPN, δοκιμάστε να το απενεργοποιήσετε και να φορτώσετε εκ νέου αυτήν τη σελίδα.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Εάν δεν λειτουργήσει αυτό, μπορείτε να παρακολουθήσετε αυτό το βίντεο στο YouTube, ωστόσο χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Το Duck Player παρέχει μια καθαρή εμπειρία προβολής χωρίς εξατομικευμένες διαφημίσεις, ενώ εμποδίζει τη δραστηριότητα προβολής να επηρεάσει τις συστάσεις που θα λαμβάνετε στο YouTube." } diff --git a/build/integration/pages/duckplayer/locales/en/duckplayer.json b/build/integration/pages/duckplayer/locales/en/duckplayer.json index c2b5683b9..fe94917c4 100644 --- a/build/integration/pages/duckplayer/locales/en/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/en/duckplayer.json @@ -33,6 +33,26 @@ "title": "ERROR: Invalid video id", "note": "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading": { + "title": "YouTube won’t let Duck Player load this video", + "note": "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1": { + "title": "YouTube doesn’t allow this video to be viewed outside of YouTube.", + "note": "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2": { + "title": "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1": { + "title": "YouTube is blocking this video from loading. If you’re using a VPN, try turning it off and reloading this page.", + "note": "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2": { + "title": "If this doesn’t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "More troubleshooting tips for this specific error" + }, "tooltipInfo": { "title": "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } diff --git a/build/integration/pages/duckplayer/locales/es/duckplayer.json b/build/integration/pages/duckplayer/locales/es/duckplayer.json index 1b5d8b958..f5af0d046 100644 --- a/build/integration/pages/duckplayer/locales/es/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/es/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERROR: ID de vídeo no válida", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube no permite que Duck Player cargue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube no permite que este vídeo se vea fuera de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional que ofrece Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube está bloqueando la carga de este vídeo. Si estás usando una VPN, intenta desactivarla y volver a cargar la página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si esto no funciona, sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." } diff --git a/build/integration/pages/duckplayer/locales/et/duckplayer.json b/build/integration/pages/duckplayer/locales/et/duckplayer.json index c9863f4f5..70b30efee 100644 --- a/build/integration/pages/duckplayer/locales/et/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/et/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIGA: vale video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei luba Duck Playeril seda videot laadida", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei luba seda videot väljaspool YouTube'i vaadata.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Saate seda videot endiselt YouTube'is vaadata, kuid ilma Duck Player'i lisatud privaatsuseta.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokeerib selle video laadimise. Kui kasutate VPN-i, proovige see välja lülitada ning leht uuesti laadida.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Kui see ei aita, saate seda videot ikkagi YouTube'is vaadata, kuid ilma Duck Playeri lisatud privaatsuseta.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pakub isikupärastatud reklaamidest vaba vaatamiskogemust ja takistab, et vaatamisaktiivsus mõjutaks sinu YouTube'i soovitusi." } diff --git a/build/integration/pages/duckplayer/locales/fi/duckplayer.json b/build/integration/pages/duckplayer/locales/fi/duckplayer.json index e73022b8f..4c830a739 100644 --- a/build/integration/pages/duckplayer/locales/fi/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/fi/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIRHE: virheellinen videotunnus", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei salli Duck Playerin ladata tätä videota.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei salli tämän videon katsomista YouTuben ulkopuolella.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Voit yhä katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube estää tämän videon latautumisen. Jos käytät VPN:ää, kytke se pois päältä ja lataa tämä sivu uudelleen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jos tämä ei toimi, voit silti katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tarjoaa puhtaan katselukokemuksen ilman kohdennettuja mainoksia ja estää katseluhistoriaa vaikuttamasta YouTube-suosituksiisi." } diff --git a/build/integration/pages/duckplayer/locales/fr/duckplayer.json b/build/integration/pages/duckplayer/locales/fr/duckplayer.json index 716f0c071..12eb0ac92 100644 --- a/build/integration/pages/duckplayer/locales/fr/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/fr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERREUR : identifiant vidéo non valide", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne permet pas à Duck Player de charger cette vidéo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube n'autorise pas le visionnage de cette vidéo en dehors de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloque le chargement de cette vidéo. Si vous utilisez un VPN, essayez de le désactiver et de recharger cette page.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si cela ne fonctionne pas, vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." } diff --git a/build/integration/pages/duckplayer/locales/hr/duckplayer.json b/build/integration/pages/duckplayer/locales/hr/duckplayer.json index 3f0e8aeae..48fdbf997 100644 --- a/build/integration/pages/duckplayer/locales/hr/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/hr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "POGREŠKA: Nevažeći ID videozapisa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dopušta Duck Playeru da učita ovaj videozapis.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dopušta da se ovaj videozapis gleda izvan YouTubea.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Još uvijek možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokira učitavanje ovog videozapisa. Ako koristiš VPN, pokušaj ga isključiti i ponovno učitati ovu stranicu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ako to ne uspije, i dalje možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pruža čisti doživljaj gledanja bez personaliziranih oglasa i sprječava da aktivnosti gledanja utječu na tvoje preporuke na YouTubeu." } diff --git a/build/integration/pages/duckplayer/locales/hu/duckplayer.json b/build/integration/pages/duckplayer/locales/hu/duckplayer.json index 3bbe06210..c8faf475f 100644 --- a/build/integration/pages/duckplayer/locales/hu/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/hu/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HIBA: Érvénytelen videoazonosító", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "A YouTube nem engedi, hogy a Duck Player betöltse ezt a videót", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "A YouTube nem engedi, hogy ezt a videót a YouTube-on kívül nézd meg.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Megnézheted a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "A YouTube blokkolja ennek a videónak a betöltését. Ha VPN-t használsz, próbáld meg, hogy kikapcsolod, majd újra betöltöd ezt az oldalt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ha ez nem működik, akkor is megnézheted ezt a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "A Duck Player személyre szabott hirdetések nélküli, letisztult megtekintési élményt nyújt, és megakadályozza, hogy a megtekintési tevékenységed befolyásolja a neked szóló YouTube-ajánlásokat." } diff --git a/build/integration/pages/duckplayer/locales/it/duckplayer.json b/build/integration/pages/duckplayer/locales/it/duckplayer.json index 9ce216677..464303fca 100644 --- a/build/integration/pages/duckplayer/locales/it/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/it/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRORE: ID video non valido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube non consente a Duck Player di caricare questo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Questo video si può vedere solo su YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Puoi ancora guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube sta impedendo il caricamento di questo video. Se stai utilizzando una VPN, prova a disattivarla e a ricaricare questa pagina.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se il problema non si risolve, puoi comunque guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." } diff --git a/build/integration/pages/duckplayer/locales/lt/duckplayer.json b/build/integration/pages/duckplayer/locales/lt/duckplayer.json index 1b282dd2c..c8dd25e38 100644 --- a/build/integration/pages/duckplayer/locales/lt/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/lt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KLAIDA: netinkamas vaizdo įrašo ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "„YouTube“ neleidžia „Duck Player“ įkelti šio vaizdo įrašo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "„YouTube“ neleidžia šio vaizdo įrašo žiūrėti ne „YouTube“ platformoje.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šį vaizdo įrašą vis dar gali žiūrėti „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "„YouTube“ blokuoja šio vaizdo įrašo įkėlimą. Jei naudoji VPN, pabandyk jį išjungti ir iš naujo įkelti šį puslapį.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jei tai neveikia, vis tiek gali žiūrėti šį vaizdo įrašą „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "„Duck Player“ užtikrina nepriekaištingą žiūrėjimo patirtį be suasmenintų reklamų ir neleidžia žiūrėjimo veiklai daryti įtakos „YouTube“ rekomendacijoms." } diff --git a/build/integration/pages/duckplayer/locales/lv/duckplayer.json b/build/integration/pages/duckplayer/locales/lv/duckplayer.json index 46d0bee8e..3ae376c31 100644 --- a/build/integration/pages/duckplayer/locales/lv/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/lv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KĻŪDA: Nederīgs video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube neļauj Duck Player ielādēt šo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube neļauj skatīties šo video ārpus YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šo videoklipu joprojām vari skatīties vietnē YouTube, taču bez papildu Duck Player konfidencialitātes.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloķē šī video ielādi. Ja tu izmanto VPN, mēģini to izslēgt un pārlādēt šo lapu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ja tas nedarbojas, joprojām vari skatīties šo video vietnē YouTube, taču bez papildu privātuma, ko nodrošina Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player nodrošina netraucētu skatīšanās pieredzi bez personalizētām reklāmām un neļauj skatīšanās darbībām ietekmēt tavus YouTube ieteikumus." } diff --git a/build/integration/pages/duckplayer/locales/nb/duckplayer.json b/build/integration/pages/duckplayer/locales/nb/duckplayer.json index 4c1d826f6..fef475447 100644 --- a/build/integration/pages/duckplayer/locales/nb/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/nb/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEIL: Ugyldig video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lar ikke Duck Player laste denne videoen", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillater ikke visning av denne videoen utenfor YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fremdeles se videoen på YouTube, men uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkerer denne videoen fra å lastes. Hvis du bruker en VPN, kan du prøve å slå den av og laste denne siden på nytt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis ikke det virker, kan du fremdeles se videoen på YouTube, bare uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tilbyr en ren seeropplevelse uten tilpassede annonser og forhindrer at seeraktiviteten din påvirker YouTube-anbefalingene dine." } diff --git a/build/integration/pages/duckplayer/locales/nl/duckplayer.json b/build/integration/pages/duckplayer/locales/nl/duckplayer.json index a1be8669e..51a0ece78 100644 --- a/build/integration/pages/duckplayer/locales/nl/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/nl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FOUT: ongeldige video-id", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube staat Duck Player niet toe om deze video te laden.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube staat niet toe dat je deze video buiten YouTube bekijkt.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Je kunt deze video nog steeds bekijken op YouTube, maar zonder de extra privacy van Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkeert het laden van deze video. Als je een VPN gebruikt, probeer deze dan uit te schakelen en deze pagina opnieuw te laden.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Als dit niet werkt, kun je deze video nog steeds op YouTube bekijken, maar dan zonder de extra privacy van Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." } diff --git a/build/integration/pages/duckplayer/locales/pl/duckplayer.json b/build/integration/pages/duckplayer/locales/pl/duckplayer.json index accd3fde5..8b633d415 100644 --- a/build/integration/pages/duckplayer/locales/pl/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/pl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "BŁĄD: nieprawidłowy identyfikator filmu", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nie zezwala na załadowanie tego filmu przez Duck Player", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nie zezwala na oglądanie tego filmu poza YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Nadal możesz oglądać ten film na YouTube, ale bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje ładowanie tego filmu. Jeśli korzystasz z sieci VPN, spróbuj ją wyłączyć i ponownie załadować tę stronę.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jeśli to nie pomoże, nadal możesz oglądać ten film na YouTube, jednak bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." } diff --git a/build/integration/pages/duckplayer/locales/pt/duckplayer.json b/build/integration/pages/duckplayer/locales/pt/duckplayer.json index a5bfca188..2ff6fad9c 100644 --- a/build/integration/pages/duckplayer/locales/pt/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/pt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRO: ID de vídeo inválido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "O YouTube não permite que o Duck Player carregue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "O YouTube não permite que vejas este vídeo fora do YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "O YouTube está a bloquear o carregamento deste vídeo. Se estiveres a usar uma VPN, tenta desativá-la e recarregar esta página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se isto não funcionar, continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." } diff --git a/build/integration/pages/duckplayer/locales/ro/duckplayer.json b/build/integration/pages/duckplayer/locales/ro/duckplayer.json index bfafec70e..25ffac535 100644 --- a/build/integration/pages/duckplayer/locales/ro/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/ro/duckplayer.json @@ -32,6 +32,26 @@ "title" : "EROARE: ID video incorect", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nu permite Duck Player să încarce acest videoclip", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nu permite ca acest videoclip să fie vizionat în afara YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blochează încărcarea acestui videoclip. Dacă folosești un VPN, încearcă să-l dezactivezi și să reîncarci această pagină.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Dacă acest lucru nu funcționează, poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player oferă o experiență de vizionare fără perturbări, fără reclame personalizate și împiedică activitatea de vizionare să îți influențeze recomandările YouTube." } diff --git a/build/integration/pages/duckplayer/locales/ru/duckplayer.json b/build/integration/pages/duckplayer/locales/ru/duckplayer.json index 4bf5cc0c1..d1a3fc528 100644 --- a/build/integration/pages/duckplayer/locales/ru/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/ru/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ОШИБКА: Неверный идентификатор видео", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube не позволяет проигрывателю Duck Player загрузить это видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволяет смотреть это видео вне своей платформы.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Вы по-прежнему можете посмотреть этот ролик на YouTube, но уже без дополнительной защиты Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокирует загрузку этого видео. Если вы используете VPN, отключите ее и перезагрузите страницу.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Если это не даст результата, вы все равно сможете просмотреть это видео на YouTube, но без дополнительной защиты конфиденциальности, обеспечиваемой Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." } diff --git a/build/integration/pages/duckplayer/locales/sk/duckplayer.json b/build/integration/pages/duckplayer/locales/sk/duckplayer.json index 88a2cc3a5..a68cdeb89 100644 --- a/build/integration/pages/duckplayer/locales/sk/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/sk/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatný identifikátor videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovolí Duck Playeru načítať toto video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nepovoľuje, aby sa toto video pozeralo mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Toto video si môžeš pozrieť aj na YouTube, ale bez dodatočného súkromia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítanie tohto videa. Ak používaš sieť VPN, skús ju vypnúť a znova načítať túto stránku.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ak to nefunguje, môžeš si toto video pozrieť aj na YouTube, ale bez dodatočnej ochrany súkromia, ktorú poskytuje Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player poskytuje čisté zobrazenie bez personalizovaných reklám a zabraňuje tomu, aby aktivita pri sledovaní ovplyvňovala vaše odporúčania v službe YouTube." } diff --git a/build/integration/pages/duckplayer/locales/sl/duckplayer.json b/build/integration/pages/duckplayer/locales/sl/duckplayer.json index 7d4a89155..977830ef4 100644 --- a/build/integration/pages/duckplayer/locales/sl/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/sl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "NAPAKA: Neveljaven ID videoposnetka", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dovoli predvajalniku Duck Player naložiti tega videa", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dovoljuje, da si ta videoposnetek ogledate zunaj YouTuba.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Ta videoposnetek si lahko še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube preprečuje nalaganje tega videoposnetka. Če uporabljate omrežje VPN, ga poskusite izklopiti in znova naložite to stran.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Če to ne deluje, si lahko ta videoposnetek še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Predvajalnik Duck Player zagotavlja čisto izkušnjo gledanja brez prilagojenih oglasov in preprečuje, da bi dejavnost gledanja vplivala na vaša priporočila v YouTubu." } diff --git a/build/integration/pages/duckplayer/locales/sv/duckplayer.json b/build/integration/pages/duckplayer/locales/sv/duckplayer.json index cb5b75e4c..444e91750 100644 --- a/build/integration/pages/duckplayer/locales/sv/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/sv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEL: Ogiltigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube låter inte Duck Player läsa in den här videon", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillåter inte att den här videon ses utanför YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockerar den här videon från att läsas in. Om du använder ett VPN kan du prova stänga av det och läsa in sidan igen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Om det inte fungerar kan du fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ger en störningsfri visningsupplevelse utan personliga annonser och förhindrar att din tittaraktivitet påverkar YouTube-rekommendationer." } diff --git a/build/integration/pages/duckplayer/locales/tr/duckplayer.json b/build/integration/pages/duckplayer/locales/tr/duckplayer.json index c23cab2bb..e733fcaa6 100644 --- a/build/integration/pages/duckplayer/locales/tr/duckplayer.json +++ b/build/integration/pages/duckplayer/locales/tr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HATA: Geçersiz video kimliği", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube, Duck Player'ın bu videoyu yüklemesine izin vermiyor", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube, bu videonun YouTube dışında izlenmesine izin vermiyor.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Bu videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bu videonun yüklenmesini engelliyor. VPN kullanıyorsanız, VPN'i kapatıp bu sayfayı yeniden yüklemeyi deneyin.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Bu işe yaramazsa, videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player, kişiselleştirilmiş reklamlar olmadan temiz bir görüntüleme deneyimi sağlar ve görüntüleme etkinliğinin YouTube önerilerinizi etkilemesini önler." } diff --git a/build/windows/pages/duckplayer/dist/index.css b/build/windows/pages/duckplayer/dist/index.css index cff98b790..7e8fbf3a0 100644 --- a/build/windows/pages/duckplayer/dist/index.css +++ b/build/windows/pages/duckplayer/dist/index.css @@ -61,6 +61,7 @@ body[data-display=app] { /* pages/duckplayer/app/components/Components.module.css */ .Components_main { + background-color: #000; color: white; max-width: 3840px; margin: 0 auto; @@ -112,7 +113,7 @@ body[data-display=app] { text-decoration: none; } [data-layout=mobile] .Button_button { - background-color: #2f2f2f; + background-color: rgba(255, 255, 255, 0.12); } .Button_button:hover, .Button_button:focus-visible { @@ -188,7 +189,7 @@ body[data-display=app] { .SwitchBarMobile_switchBar { display: grid; border-radius: 8px; - background: #2f2f2f; + background: rgba(255, 255, 255, 0.12); padding-inline: 16px; height: 100%; line-height: 1.1; @@ -518,6 +519,9 @@ body[data-display=app] { .Wordmark_mobile_logo { height: 100px; } + [data-youtube-error=true] .Wordmark_mobile_logo { + height: 44px; + } } .Wordmark_mobile_logoSvg img { display: block; @@ -589,6 +593,127 @@ body[data-display=app] { } } +/* pages/duckplayer/app/components/YouTubeError.module.css */ +.YouTubeError_error { + align-items: center; + background: rgba(0, 0, 0, 0.6); + display: grid; + height: 100%; + justify-items: center; +} +.YouTubeError_error.YouTubeError_desktop { + height: var(--frame-height); + overflow: hidden; + position: relative; + z-index: 1; +} +.YouTubeError_error.YouTubeError_mobile { + border-radius: var(--inner-radius); + height: 100%; + overflow: auto; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_error.YouTubeError_mobile { + aspect-ratio: 16 / 9; + } +} +.YouTubeError_desktop { + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.YouTubeError_container { + column-gap: 24px; + display: flex; + flex-flow: row; + margin: 0; + max-width: 680px; + padding: 0 40px; + row-gap: 4px; +} +.YouTubeError_mobile .YouTubeError_container { + flex-flow: column; + padding: 0 24px; +} +@media screen and (min-height: 320px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 16px 0; + } +} +@media screen and (min-width: 375px) and (min-height: 400px) { + .YouTubeError_mobile .YouTubeError_container { + margin: 36px 0; + } +} +.YouTubeError_content { + display: flex; + flex-direction: column; + gap: 4px; + margin: 16px 0; +} +@media screen and (min-width: 600px) { + .YouTubeError_content { + margin: 24px 0; + } +} +.YouTubeError_icon { + align-self: center; + display: flex; + justify-content: center; +} +.YouTubeError_icon::before { + content: " "; + display: block; + background: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A%0A') no-repeat; + height: 48px; + width: 48px; +} +@media screen and (max-width: 320px) { + .YouTubeError_icon { + display: none; + } +} +@media screen and (min-width: 600px) and (min-height: 600px) { + .YouTubeError_icon { + justify-content: start; + } + .YouTubeError_icon::before { + background-image: url('data:image/svg+xml,%0A %0A %0A %0A %0A %0A %0A%0A'); + height: 96px; + width: 128px; + } +} +.YouTubeError_heading { + color: #fff; + font-size: 20px; + font-weight: 700; + line-height: calc(24 / 20); + margin: 0; +} +.YouTubeError_messages { + color: #ccc; + font-size: 16px; + line-height: calc(24 / 16); +} +div.YouTubeError_messages { + display: flex; + flex-direction: column; + gap: 24px; +} +div.YouTubeError_messages p { + margin: 0; +} +p.YouTubeError_messages { + margin: 0; +} +ul.YouTubeError_messages li { + list-style: disc; + margin-left: 24px; +} + /* pages/duckplayer/app/components/MobileApp.module.css */ body[data-display=app] { padding: 8px; @@ -626,6 +751,7 @@ html[data-focus-mode=on] .MobileApp_hideInFocus { --inner-radius: 12px; --logo-width: 157px; --inner-padding: 8px; + --mobile-buttons-padding: 8px; position: relative; max-width: 100vh; margin: 0 auto; @@ -681,6 +807,15 @@ html[data-focus-mode=on] .MobileApp_embed { grid-area: switch; height: 44px; } +.MobileApp_detachedControls { + grid-area: detached; + display: flex; + flex-flow: column; + gap: 8px; + padding: 8px; + background: #2f2f2f; + border-radius: 12px; +} @media screen and (min-width: 425px) and (max-height: 600px) { .MobileApp_main { grid-template-rows: max-content auto max-content max-content 12px max-content auto; @@ -774,6 +909,71 @@ html[data-focus-mode=on] .MobileApp_embed { justify-content: end; } } +@media screen and (max-width: 599px) { + .MobileApp_main[data-youtube-error=true] { + --bg-color: transparent; + --inner-padding: 4px; + grid-template-areas: "logo" "gap3" "embed" "gap4" "switch" "buttons"; + grid-template-rows: max-content 16px auto 12px max-content max-content; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + background: #2f2f2f; + border-radius: var(--outer-radius); + padding: 4px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_switch { + background: #2f2f2f; + padding: 8px 8px 0 8px; + height: 60px; + max-height: 60px; + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + background: #2f2f2f; + padding: 8px; + transition: all 0.3s; + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_buttons { + border-radius: var(--outer-radius); + } + .MobileApp_main[data-youtube-error=true]:has([data-state=completed]) .MobileApp_switch { + background: transparent; + max-height: 0; + } +} +@media screen and (max-width: 599px) and (max-height: 599px) { + .MobileApp_main[data-youtube-error=true] { + max-width: unset; + grid-template-rows: 0 0 auto 12px 0 max-content; + } + .MobileApp_main[data-youtube-error=true] :is(.MobileApp_logo, .MobileApp_switch) { + display: none; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + } +} +@media screen and (min-width: 600px) and (max-height: 450px) { + .MobileApp_main[data-youtube-error=true] { + grid-template-areas: "embed" "buttons" "gap5"; + grid-template-rows: auto max-content 8px; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + border-radius: var(--outer-radius); + display: block; + } +} +@media screen and (max-height: 320px) { + .MobileApp_main[data-youtube-error=true] .MobileApp_embed { + overflow-y: auto; + } + .MobileApp_main[data-youtube-error=true] .MobileApp_buttons { + bottom: 0; + position: sticky; + } +} /* pages/duckplayer/app/components/MobileButtons.module.css */ .MobileButtons_buttons { diff --git a/build/windows/pages/duckplayer/dist/index.js b/build/windows/pages/duckplayer/dist/index.js index 64928541f..430aabad4 100644 --- a/build/windows/pages/duckplayer/dist/index.js +++ b/build/windows/pages/duckplayer/dist/index.js @@ -1982,12 +1982,33 @@ title: "ERROR: Invalid video id", note: "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + blockedVideoErrorHeading: { + title: "YouTube won\u2019t let Duck Player load this video", + note: "Message shown when YouTube has blocked playback of a video" + }, + blockedVideoErrorMessage1: { + title: "YouTube doesn\u2019t allow this video to be viewed outside of YouTube.", + note: "Explanation on why the error is happening." + }, + blockedVideoErrorMessage2: { + title: "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "A message explaining that the blocked video can be watched directly on YouTube." + }, + signInRequiredErrorMessage1: { + title: "YouTube is blocking this video from loading. If you\u2019re using a VPN, try turning it off and reloading this page.", + note: "Explanation on why the error is happening and a suggestions on how to solve it." + }, + signInRequiredErrorMessage2: { + title: "If this doesn\u2019t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + note: "More troubleshooting tips for this specific error" + }, tooltipInfo: { title: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } }; // pages/duckplayer/app/settings.js + var DEFAULT_SIGN_IN_REQURED_HREF = '[href*="//support.google.com/youtube/answer/3037019"]'; var Settings = class _Settings { /** * @param {object} params @@ -1995,17 +2016,20 @@ * @param {{state: 'enabled' | 'disabled'}} [params.pip] * @param {{state: 'enabled' | 'disabled'}} [params.autoplay] * @param {{state: 'enabled' | 'disabled'}} [params.focusMode] + * @param {import("../types/duckplayer.js").InitialSetupResponse['settings']['customError']} [params.customError] */ constructor({ platform = { name: "macos" }, pip = { state: "disabled" }, autoplay = { state: "enabled" }, - focusMode = { state: "enabled" } + focusMode = { state: "enabled" }, + customError = { state: "disabled", signInRequiredSelector: "" } }) { this.platform = platform; this.pip = pip; this.autoplay = autoplay; this.focusMode = focusMode; + this.customError = customError; } /** * @param {keyof import("../types/duckplayer.js").DuckPlayerPageSettings} named @@ -2014,7 +2038,7 @@ */ withFeatureState(named, settings) { if (!settings) return this; - const valid = ["pip", "autoplay", "focusMode"]; + const valid = ["pip", "autoplay", "focusMode", "customError"]; if (!valid.includes(named)) { console.warn(`Excluding invalid feature key ${named}`); return this; @@ -2053,6 +2077,28 @@ } return this; } + /** + * @param {string|null|undefined} newState + * @return {Settings} + */ + withCustomError(newState) { + if (newState === "disabled") { + return new _Settings({ + ...this, + customError: { state: "disabled" } + }); + } + if (newState === "enabled") { + return new _Settings({ + ...this, + customError: { + state: "enabled", + signOnRequiredSelector: DEFAULT_SIGN_IN_REQURED_HREF + } + }); + } + return this; + } /** * @return {string} */ @@ -2959,6 +3005,162 @@ } }; + // pages/duckplayer/app/providers/YouTubeErrorProvider.jsx + var YOUTUBE_ERROR_EVENT = "ddg-duckplayer-youtube-error"; + var YOUTUBE_ERRORS = { + ageRestricted: "age-restricted", + signInRequired: "sign-in-required", + noEmbed: "no-embed", + unknown: "unknown" + }; + var YOUTUBE_ERROR_IDS = Object.values(YOUTUBE_ERRORS); + var YouTubeErrorContext = J({ + /** @type {YouTubeError|null} */ + error: null + }); + function YouTubeErrorProvider({ initial = null, children }) { + let initialError = null; + if (initial && YOUTUBE_ERROR_IDS.includes(initial)) { + initialError = initial; + } + const [error, setError] = h2(initialError); + const messaging2 = useMessaging(); + const platformName = usePlatformName(); + const setFocusMode = useSetFocusMode(); + y2(() => { + const errorEventHandler = (event) => { + const eventError = event.detail?.error; + if (YOUTUBE_ERROR_IDS.includes(eventError) || eventError === null) { + if (eventError && eventError !== error) { + setFocusMode("paused"); + if (platformName === "macos" || platformName === "ios") { + messaging2.reportYouTubeError({ error: eventError }); + } + } else { + setFocusMode("enabled"); + } + setError(eventError); + } + }; + window.addEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + return () => window.removeEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + }, []); + return /* @__PURE__ */ g(YouTubeErrorContext.Provider, { value: { error } }, children); + } + function useYouTubeError() { + return x2(YouTubeErrorContext).error; + } + + // pages/duckplayer/app/features/error-detection.js + var ErrorDetection = class { + /** @type {HTMLIFrameElement} */ + iframe; + /** @type {CustomErrorOptions} */ + options; + /** + * @param {CustomErrorOptions} options + */ + constructor(options) { + this.options = options; + } + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + this.iframe = iframe; + if (!this.options || !this.options.signInRequiredSelector) { + console.log("Missing Custom Error options"); + return null; + } + const documentBody = iframe.contentWindow?.document?.body; + if (documentBody) { + if (this.checkForError(documentBody)) { + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + return null; + } + const observer = new MutationObserver(this.handleMutation.bind(this)); + observer.observe(documentBody, { + childList: true, + subtree: true + // Observe all descendants of the body + }); + return () => { + observer.disconnect(); + }; + } + return null; + } + /** + * Mutation handler that checks new nodes for error states + * + * @type {MutationCallback} + */ + handleMutation(mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { + if (this.checkForError(node)) { + console.log("A node with an error has been added to the document:", node); + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + } + }); + } + } + } + /** + * Attempts to detect the type of error in the YouTube embed iframe + * @returns {YouTubeError} + */ + getErrorType() { + const iframeWindow = ( + /** @type {Window & { ytcfg: object }} */ + this.iframe.contentWindow + ); + let playerResponse; + try { + playerResponse = JSON.parse(iframeWindow.ytcfg?.get("PLAYER_VARS")?.embedded_player_response); + } catch (e3) { + console.log("Could not parse player response", e3); + } + if (typeof playerResponse === "object") { + const { + previewPlayabilityStatus: { desktopLegacyAgeGateReason, status } + } = playerResponse; + if (status === "UNPLAYABLE") { + if (desktopLegacyAgeGateReason === 1) { + return YOUTUBE_ERRORS.ageRestricted; + } + return YOUTUBE_ERRORS.noEmbed; + } + try { + if (this.options?.signInRequiredSelector && !!iframeWindow.document.querySelector(this.options.signInRequiredSelector)) { + return YOUTUBE_ERRORS.signInRequired; + } + } catch (e3) { + console.log("Sign-in required query failed", e3); + } + } + return YOUTUBE_ERRORS.unknown; + } + /** + * Analyses a node and its children to determine if it contains an error state + * + * @param {Node} [node] + */ + checkForError(node) { + if (node?.nodeType === Node.ELEMENT_NODE) { + const element = ( + /** @type {HTMLElement} */ + node + ); + return element.classList.contains("ytp-error") || !!element.querySelector("ytp-error"); + } + return false; + } + }; + // pages/duckplayer/app/features/iframe.js var IframeFeature = class { /** @@ -3015,6 +3217,12 @@ */ mouseCapture: () => { return new MouseCapture(); + }, + /** + * @return {IframeFeature} + */ + errorDetection: () => { + return new ErrorDetection(settings.customError); } }; } @@ -3083,7 +3291,8 @@ features.pip(), features.clickCapture(), features.titleCapture(), - features.mouseCapture() + features.mouseCapture(), + features.errorDetection() ]; const cleanups = []; const loadHandler = () => { @@ -3110,6 +3319,48 @@ return { ref, didLoad: () => didLoad.current = true }; } + // pages/duckplayer/app/components/YouTubeError.jsx + var import_classnames10 = __toESM(require_classnames(), 1); + + // pages/duckplayer/app/components/YouTubeError.module.css + var YouTubeError_default = { + error: "YouTubeError_error", + desktop: "YouTubeError_desktop", + mobile: "YouTubeError_mobile", + container: "YouTubeError_container", + content: "YouTubeError_content", + icon: "YouTubeError_icon", + heading: "YouTubeError_heading", + messages: "YouTubeError_messages" + }; + + // pages/duckplayer/app/components/YouTubeError.jsx + function useErrorStrings(kind) { + const { t: t3 } = useTypedTranslation(); + switch (kind) { + case "sign-in-required": + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("signInRequiredErrorMessage1"), t3("signInRequiredErrorMessage2")], + variant: "paragraphs" + }; + default: + return { + heading: t3("blockedVideoErrorHeading"), + messages: [t3("blockedVideoErrorMessage1"), t3("blockedVideoErrorMessage2")], + variant: "paragraphs" + }; + } + } + function YouTubeError({ kind, layout }) { + const { heading, messages, variant } = useErrorStrings(kind); + const classes = (0, import_classnames10.default)(YouTubeError_default.error, { + [YouTubeError_default.desktop]: layout === "desktop", + [YouTubeError_default.mobile]: layout === "mobile" + }); + return /* @__PURE__ */ g("div", { className: classes }, /* @__PURE__ */ g("div", { className: YouTubeError_default.container }, /* @__PURE__ */ g("span", { className: YouTubeError_default.icon }), /* @__PURE__ */ g("div", { className: YouTubeError_default.content }, /* @__PURE__ */ g("h1", { className: YouTubeError_default.heading }, heading), messages && variant === "inline" && /* @__PURE__ */ g("p", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("span", { key: item }, item))), messages && variant === "paragraphs" && /* @__PURE__ */ g("div", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("p", { key: item }, item))), messages && variant === "list" && /* @__PURE__ */ g("ul", { className: YouTubeError_default.messages }, messages.map((item) => /* @__PURE__ */ g("li", { key: item }, item)))))); + } + // pages/duckplayer/app/components/Components.jsx function Components() { const settings = new Settings({ @@ -3118,11 +3369,11 @@ let embed = EmbedSettings.fromHref("https://localhost?videoID=123"); let url = embed?.toEmbedUrl(); if (!url) throw new Error("unreachable"); - return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); + return /* @__PURE__ */ g(k, null, /* @__PURE__ */ g("main", { class: Components_default.main }, /* @__PURE__ */ g("div", { class: Components_default.tube }, /* @__PURE__ */ g(Wordmark, null), /* @__PURE__ */ g("h2", null, "Floating Bar"), /* @__PURE__ */ g("div", { style: "position: relative; padding-left: 10em; min-height: 150px;" }, /* @__PURE__ */ g(InfoIcon, { debugStyles: true })), /* @__PURE__ */ g("h2", null, "Info Tooltip"), /* @__PURE__ */ g(FloatingBar, null, /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: info_data_default })), /* @__PURE__ */ g(Button, { icon: true }, /* @__PURE__ */ g(Icon, { src: cog_data_default })), /* @__PURE__ */ g(Button, { fill: true }, "Open in YouTube")), /* @__PURE__ */ g("h2", null, "Info Bar"), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(InfoBar, { embed }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (ios)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" })), /* @__PURE__ */ g("h2", null, "Mobile Switch Bar (android)"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName: "android" })), /* @__PURE__ */ g("h2", null, "Desktop Switch bar"), /* @__PURE__ */ g("h3", null, "idle"), /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarDesktop, null))), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=false (desktop)")), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(Player, { src: url, layout: "desktop" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "sign-in-required" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(PlayerContainer, null, /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: "no-embed" }), /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed })))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g("h2", null, /* @__PURE__ */ g("code", null, "inset=true (mobile)")), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "sign-in-required" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null), /* @__PURE__ */ g(PlayerContainer, { inset: true }, /* @__PURE__ */ g(PlayerInternal, { inset: true }, /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: "no-embed" }), /* @__PURE__ */ g(SwitchBarMobile, { platformName: "ios" }))), /* @__PURE__ */ g("br", null))); } // pages/duckplayer/app/components/MobileApp.jsx - var import_classnames10 = __toESM(require_classnames(), 1); + var import_classnames11 = __toESM(require_classnames(), 1); // pages/duckplayer/app/components/MobileApp.module.css var MobileApp_default = { @@ -3133,7 +3384,8 @@ switch: "MobileApp_switch", embed: "MobileApp_embed", logo: "MobileApp_logo", - buttons: "MobileApp_buttons" + buttons: "MobileApp_buttons", + detachedControls: "MobileApp_detachedControls" }; // pages/duckplayer/app/features/app.js @@ -3231,11 +3483,13 @@ function MobileApp({ embed }) { const settings = useSettings(); const telemetry2 = useTelemetry(); + const youtubeError = useYouTubeError(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g( + return /* @__PURE__ */ g(k, null, !youtubeError && features.focusMode(), /* @__PURE__ */ g( OrientationProvider, { onChange: (orientation) => { + if (youtubeError) return; if (orientation === "portrait") { return FocusMode.enable(); } @@ -3251,7 +3505,10 @@ } function MobileLayout({ embed }) { const platformName = usePlatformName(); - return /* @__PURE__ */ g("main", { class: MobileApp_default.main }, /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames10.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("main", { class: MobileApp_default.main, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.filler, MobileApp_default.hideInFocus) }), /* @__PURE__ */ g("div", { class: MobileApp_default.embed }, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "mobile", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "mobile", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "mobile" })), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.logo, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileWordmark, null)), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.switch, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(SwitchProvider, null, /* @__PURE__ */ g(SwitchBarMobile, { platformName }))), /* @__PURE__ */ g("div", { class: (0, import_classnames11.default)(MobileApp_default.buttons, MobileApp_default.hideInFocus) }, /* @__PURE__ */ g(MobileButtons, { embed }))); } // pages/duckplayer/app/components/DesktopApp.module.css @@ -3272,10 +3529,14 @@ function DesktopApp({ embed }) { const settings = useSettings(); const features = createAppFeaturesFrom(settings); - return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app }, /* @__PURE__ */ g(DesktopLayout, { embed }))); + const youtubeError = useYouTubeError(); + return /* @__PURE__ */ g(k, null, features.focusMode(), /* @__PURE__ */ g("main", { class: DesktopApp_default.app, "data-youtube-error": !!youtubeError }, /* @__PURE__ */ g(DesktopLayout, { embed }))); } function DesktopLayout({ embed }) { - return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === "enabled"; + return /* @__PURE__ */ g("div", { class: DesktopApp_default.desktop }, /* @__PURE__ */ g(PlayerContainer, null, embed === null && /* @__PURE__ */ g(PlayerError, { layout: "desktop", kind: "invalid-id" }), embed !== null && showCustomError && /* @__PURE__ */ g(YouTubeError, { layout: "desktop", kind: youtubeError }), embed !== null && !showCustomError && /* @__PURE__ */ g(Player, { src: embed.toEmbedUrl(), layout: "desktop" }), /* @__PURE__ */ g(HideInFocusMode, { style: "slide" }, /* @__PURE__ */ g(InfoBarContainer, null, /* @__PURE__ */ g(InfoBar, { embed }))))); } // pages/duckplayer/app/index.js @@ -3291,7 +3552,11 @@ console.log("locale:", environment.locale); document.body.dataset.display = environment.display; const strings = environment.locale === "en" ? duckplayer_default : await getTranslationsFromStringOrLoadDynamically(init2.localeStrings, environment.locale) || duckplayer_default; - const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")); + const settings = new Settings({}).withPlatformName(baseEnvironment2.injectName).withPlatformName(init2.platform?.name).withPlatformName(baseEnvironment2.urlParams.get("platform")).withFeatureState("pip", init2.settings.pip).withFeatureState("autoplay", init2.settings.autoplay).withFeatureState("focusMode", init2.settings.focusMode).withFeatureState("customError", init2.settings.customError).withDisabledFocusMode(baseEnvironment2.urlParams.get("focusMode")).withCustomError(baseEnvironment2.urlParams.get("customError")); + const initialYouTubeError = ( + /** @type {YouTubeError} */ + baseEnvironment2.urlParams.get("youtubeError") + ); console.log(settings); const embed = createEmbedSettings(window.location.href, settings); const didCatch = (error) => { @@ -3303,7 +3568,7 @@ if (!root) throw new Error("could not render, root element missing"); if (environment.display === "app") { D( - /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( + /* @__PURE__ */ g(EnvironmentProvider, { debugState: environment.debugState, injectName: environment.injectName, willThrow: environment.willThrow }, /* @__PURE__ */ g(ErrorBoundary, { didCatch, fallback: /* @__PURE__ */ g(Fallback, { showDetails: environment.env === "development" }) }, /* @__PURE__ */ g(UpdateEnvironment, { search: window.location.search }), /* @__PURE__ */ g(TelemetryContext.Provider, { value: telemetry2 }, /* @__PURE__ */ g(MessagingContext2.Provider, { value: messaging2 }, /* @__PURE__ */ g(SettingsProvider, { settings }, /* @__PURE__ */ g(YouTubeErrorProvider, { initial: initialYouTubeError }, /* @__PURE__ */ g(UserValuesProvider, { initial: init2.userValues }, settings.layout === "desktop" && /* @__PURE__ */ g( TranslationProvider, { translationObject: duckplayer_default, @@ -3319,7 +3584,7 @@ textLength: environment.textLength }, /* @__PURE__ */ g(MobileApp, { embed }) - ), /* @__PURE__ */ g(WillThrow, null))))))), + ), /* @__PURE__ */ g(WillThrow, null)))))))), root ); } else if (environment.display === "components") { @@ -3467,6 +3732,13 @@ onUserValuesChanged(cb) { return this.messaging.subscribe("onUserValuesChanged", cb); } + /** + * This will be sent if the application fails to load. + * @param {{error: import('../types/duckplayer.ts').YouTubeError}} params + */ + reportYouTubeError(params) { + this.messaging.notify("reportYouTubeError", params); + } /** * This will be sent if the application has loaded, but a client-side error * has occurred that cannot be recovered from diff --git a/build/windows/pages/duckplayer/locales/bg/duckplayer.json b/build/windows/pages/duckplayer/locales/bg/duckplayer.json index d7f3d20ef..c807a2f78 100644 --- a/build/windows/pages/duckplayer/locales/bg/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/bg/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ГРЕШКА: невалиден идентификатор на видеоклипа", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube няма да позволи на Duck Player да зареди това видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволява това видео да бъде гледано извън YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Все пак можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокира зареждането на това видео. Ако използвате VPN, опитайте да го изключите и презаредете тази страница.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ако това не свърши работа, можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player осигурява чисто изживяване без персонализирани реклами в YouTube и предотвратява влиянието на вече гледаните видеоклипове върху препоръките на YouTube." } diff --git a/build/windows/pages/duckplayer/locales/cs/duckplayer.json b/build/windows/pages/duckplayer/locales/cs/duckplayer.json index 8d24c5726..690569a5d 100644 --- a/build/windows/pages/duckplayer/locales/cs/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/cs/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatné ID videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovoluje přehrávači Duck Player načíst tohle video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nedovoluje spuštění videa mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Na tohle video se můžeš pořád podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítání tohohle videa. Pokud používáš VPN, zkus ji vypnout a stránku znovu načíst.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Pokud to nefunguje, můžeš se na video podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Přehrávač Duck Player nabízí sledování v minimalistickém prostředí bez personalizovaných reklam a brání tomu, aby sledovaná videa ovlivňovala tvoje doporučení na YouTube." } diff --git a/build/windows/pages/duckplayer/locales/da/duckplayer.json b/build/windows/pages/duckplayer/locales/da/duckplayer.json index f99ab298e..aae6004c2 100644 --- a/build/windows/pages/duckplayer/locales/da/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/da/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEJL: Ugyldigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube vil ikke lade Duck Player indlæse denne video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillader ikke, at denne video vises uden for YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokerer for, at denne video kan indlæses. Hvis du bruger en VPN, så prøv at slå den fra og genindlæse denne side.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis dette ikke virker, kan du stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player giver en ren seeroplevelse uden målrettede annoncer og forhindrer, at visningsaktivitet påvirker dine YouTube-anbefalinger." } diff --git a/build/windows/pages/duckplayer/locales/de/duckplayer.json b/build/windows/pages/duckplayer/locales/de/duckplayer.json index 0ddca103a..abd163119 100644 --- a/build/windows/pages/duckplayer/locales/de/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/de/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEHLER: Ungültige Video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lässt den Duck Player dieses Video nicht laden", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube erlaubt nicht, dass dieses Video außerhalb von YouTube angesehen wird.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kannst dieses Video auf YouTube ansehen, aber ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockiert das Laden dieses Videos. Falls du ein VPN benutzt, deaktiviere es und lade diese Seite neu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Falls das nicht funktioniert, kannst du das Video dennoch auf YouTube ansehen, jedoch ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." } diff --git a/build/windows/pages/duckplayer/locales/el/duckplayer.json b/build/windows/pages/duckplayer/locales/el/duckplayer.json index d00195f5e..2d8fa43c5 100644 --- a/build/windows/pages/duckplayer/locales/el/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/el/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ΣΦΑΛΜΑ: Μη έγκυρο αναγνωριστικό βίντεο", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "Το YouTube δεν θα αφήσει το Duck Player να φορτώσει το βίντεο αυτό", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Το YouTube δεν επιτρέπει την προβολή αυτού του βίντεο εκτός YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Μπορείτε ακόμα να παρακολουθήσετε αυτό το βίντεο στο YouTube, αλλά χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "Το YouTube μπλοκάρει τη φόρτωση αυτού του βίντεο. Εάν χρησιμοποιείτε VPN, δοκιμάστε να το απενεργοποιήσετε και να φορτώσετε εκ νέου αυτήν τη σελίδα.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Εάν δεν λειτουργήσει αυτό, μπορείτε να παρακολουθήσετε αυτό το βίντεο στο YouTube, ωστόσο χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Το Duck Player παρέχει μια καθαρή εμπειρία προβολής χωρίς εξατομικευμένες διαφημίσεις, ενώ εμποδίζει τη δραστηριότητα προβολής να επηρεάσει τις συστάσεις που θα λαμβάνετε στο YouTube." } diff --git a/build/windows/pages/duckplayer/locales/en/duckplayer.json b/build/windows/pages/duckplayer/locales/en/duckplayer.json index c2b5683b9..fe94917c4 100644 --- a/build/windows/pages/duckplayer/locales/en/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/en/duckplayer.json @@ -33,6 +33,26 @@ "title": "ERROR: Invalid video id", "note": "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading": { + "title": "YouTube won’t let Duck Player load this video", + "note": "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1": { + "title": "YouTube doesn’t allow this video to be viewed outside of YouTube.", + "note": "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2": { + "title": "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1": { + "title": "YouTube is blocking this video from loading. If you’re using a VPN, try turning it off and reloading this page.", + "note": "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2": { + "title": "If this doesn’t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "More troubleshooting tips for this specific error" + }, "tooltipInfo": { "title": "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } diff --git a/build/windows/pages/duckplayer/locales/es/duckplayer.json b/build/windows/pages/duckplayer/locales/es/duckplayer.json index 1b5d8b958..f5af0d046 100644 --- a/build/windows/pages/duckplayer/locales/es/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/es/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERROR: ID de vídeo no válida", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube no permite que Duck Player cargue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube no permite que este vídeo se vea fuera de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional que ofrece Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube está bloqueando la carga de este vídeo. Si estás usando una VPN, intenta desactivarla y volver a cargar la página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si esto no funciona, sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." } diff --git a/build/windows/pages/duckplayer/locales/et/duckplayer.json b/build/windows/pages/duckplayer/locales/et/duckplayer.json index c9863f4f5..70b30efee 100644 --- a/build/windows/pages/duckplayer/locales/et/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/et/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIGA: vale video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei luba Duck Playeril seda videot laadida", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei luba seda videot väljaspool YouTube'i vaadata.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Saate seda videot endiselt YouTube'is vaadata, kuid ilma Duck Player'i lisatud privaatsuseta.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokeerib selle video laadimise. Kui kasutate VPN-i, proovige see välja lülitada ning leht uuesti laadida.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Kui see ei aita, saate seda videot ikkagi YouTube'is vaadata, kuid ilma Duck Playeri lisatud privaatsuseta.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pakub isikupärastatud reklaamidest vaba vaatamiskogemust ja takistab, et vaatamisaktiivsus mõjutaks sinu YouTube'i soovitusi." } diff --git a/build/windows/pages/duckplayer/locales/fi/duckplayer.json b/build/windows/pages/duckplayer/locales/fi/duckplayer.json index e73022b8f..4c830a739 100644 --- a/build/windows/pages/duckplayer/locales/fi/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/fi/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIRHE: virheellinen videotunnus", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei salli Duck Playerin ladata tätä videota.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei salli tämän videon katsomista YouTuben ulkopuolella.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Voit yhä katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube estää tämän videon latautumisen. Jos käytät VPN:ää, kytke se pois päältä ja lataa tämä sivu uudelleen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jos tämä ei toimi, voit silti katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tarjoaa puhtaan katselukokemuksen ilman kohdennettuja mainoksia ja estää katseluhistoriaa vaikuttamasta YouTube-suosituksiisi." } diff --git a/build/windows/pages/duckplayer/locales/fr/duckplayer.json b/build/windows/pages/duckplayer/locales/fr/duckplayer.json index 716f0c071..12eb0ac92 100644 --- a/build/windows/pages/duckplayer/locales/fr/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/fr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERREUR : identifiant vidéo non valide", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne permet pas à Duck Player de charger cette vidéo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube n'autorise pas le visionnage de cette vidéo en dehors de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloque le chargement de cette vidéo. Si vous utilisez un VPN, essayez de le désactiver et de recharger cette page.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si cela ne fonctionne pas, vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." } diff --git a/build/windows/pages/duckplayer/locales/hr/duckplayer.json b/build/windows/pages/duckplayer/locales/hr/duckplayer.json index 3f0e8aeae..48fdbf997 100644 --- a/build/windows/pages/duckplayer/locales/hr/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/hr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "POGREŠKA: Nevažeći ID videozapisa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dopušta Duck Playeru da učita ovaj videozapis.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dopušta da se ovaj videozapis gleda izvan YouTubea.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Još uvijek možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokira učitavanje ovog videozapisa. Ako koristiš VPN, pokušaj ga isključiti i ponovno učitati ovu stranicu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ako to ne uspije, i dalje možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pruža čisti doživljaj gledanja bez personaliziranih oglasa i sprječava da aktivnosti gledanja utječu na tvoje preporuke na YouTubeu." } diff --git a/build/windows/pages/duckplayer/locales/hu/duckplayer.json b/build/windows/pages/duckplayer/locales/hu/duckplayer.json index 3bbe06210..c8faf475f 100644 --- a/build/windows/pages/duckplayer/locales/hu/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/hu/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HIBA: Érvénytelen videoazonosító", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "A YouTube nem engedi, hogy a Duck Player betöltse ezt a videót", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "A YouTube nem engedi, hogy ezt a videót a YouTube-on kívül nézd meg.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Megnézheted a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "A YouTube blokkolja ennek a videónak a betöltését. Ha VPN-t használsz, próbáld meg, hogy kikapcsolod, majd újra betöltöd ezt az oldalt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ha ez nem működik, akkor is megnézheted ezt a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "A Duck Player személyre szabott hirdetések nélküli, letisztult megtekintési élményt nyújt, és megakadályozza, hogy a megtekintési tevékenységed befolyásolja a neked szóló YouTube-ajánlásokat." } diff --git a/build/windows/pages/duckplayer/locales/it/duckplayer.json b/build/windows/pages/duckplayer/locales/it/duckplayer.json index 9ce216677..464303fca 100644 --- a/build/windows/pages/duckplayer/locales/it/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/it/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRORE: ID video non valido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube non consente a Duck Player di caricare questo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Questo video si può vedere solo su YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Puoi ancora guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube sta impedendo il caricamento di questo video. Se stai utilizzando una VPN, prova a disattivarla e a ricaricare questa pagina.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se il problema non si risolve, puoi comunque guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." } diff --git a/build/windows/pages/duckplayer/locales/lt/duckplayer.json b/build/windows/pages/duckplayer/locales/lt/duckplayer.json index 1b282dd2c..c8dd25e38 100644 --- a/build/windows/pages/duckplayer/locales/lt/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/lt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KLAIDA: netinkamas vaizdo įrašo ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "„YouTube“ neleidžia „Duck Player“ įkelti šio vaizdo įrašo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "„YouTube“ neleidžia šio vaizdo įrašo žiūrėti ne „YouTube“ platformoje.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šį vaizdo įrašą vis dar gali žiūrėti „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "„YouTube“ blokuoja šio vaizdo įrašo įkėlimą. Jei naudoji VPN, pabandyk jį išjungti ir iš naujo įkelti šį puslapį.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jei tai neveikia, vis tiek gali žiūrėti šį vaizdo įrašą „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "„Duck Player“ užtikrina nepriekaištingą žiūrėjimo patirtį be suasmenintų reklamų ir neleidžia žiūrėjimo veiklai daryti įtakos „YouTube“ rekomendacijoms." } diff --git a/build/windows/pages/duckplayer/locales/lv/duckplayer.json b/build/windows/pages/duckplayer/locales/lv/duckplayer.json index 46d0bee8e..3ae376c31 100644 --- a/build/windows/pages/duckplayer/locales/lv/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/lv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KĻŪDA: Nederīgs video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube neļauj Duck Player ielādēt šo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube neļauj skatīties šo video ārpus YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šo videoklipu joprojām vari skatīties vietnē YouTube, taču bez papildu Duck Player konfidencialitātes.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloķē šī video ielādi. Ja tu izmanto VPN, mēģini to izslēgt un pārlādēt šo lapu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ja tas nedarbojas, joprojām vari skatīties šo video vietnē YouTube, taču bez papildu privātuma, ko nodrošina Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player nodrošina netraucētu skatīšanās pieredzi bez personalizētām reklāmām un neļauj skatīšanās darbībām ietekmēt tavus YouTube ieteikumus." } diff --git a/build/windows/pages/duckplayer/locales/nb/duckplayer.json b/build/windows/pages/duckplayer/locales/nb/duckplayer.json index 4c1d826f6..fef475447 100644 --- a/build/windows/pages/duckplayer/locales/nb/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/nb/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEIL: Ugyldig video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lar ikke Duck Player laste denne videoen", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillater ikke visning av denne videoen utenfor YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fremdeles se videoen på YouTube, men uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkerer denne videoen fra å lastes. Hvis du bruker en VPN, kan du prøve å slå den av og laste denne siden på nytt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis ikke det virker, kan du fremdeles se videoen på YouTube, bare uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tilbyr en ren seeropplevelse uten tilpassede annonser og forhindrer at seeraktiviteten din påvirker YouTube-anbefalingene dine." } diff --git a/build/windows/pages/duckplayer/locales/nl/duckplayer.json b/build/windows/pages/duckplayer/locales/nl/duckplayer.json index a1be8669e..51a0ece78 100644 --- a/build/windows/pages/duckplayer/locales/nl/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/nl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FOUT: ongeldige video-id", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube staat Duck Player niet toe om deze video te laden.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube staat niet toe dat je deze video buiten YouTube bekijkt.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Je kunt deze video nog steeds bekijken op YouTube, maar zonder de extra privacy van Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkeert het laden van deze video. Als je een VPN gebruikt, probeer deze dan uit te schakelen en deze pagina opnieuw te laden.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Als dit niet werkt, kun je deze video nog steeds op YouTube bekijken, maar dan zonder de extra privacy van Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." } diff --git a/build/windows/pages/duckplayer/locales/pl/duckplayer.json b/build/windows/pages/duckplayer/locales/pl/duckplayer.json index accd3fde5..8b633d415 100644 --- a/build/windows/pages/duckplayer/locales/pl/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/pl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "BŁĄD: nieprawidłowy identyfikator filmu", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nie zezwala na załadowanie tego filmu przez Duck Player", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nie zezwala na oglądanie tego filmu poza YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Nadal możesz oglądać ten film na YouTube, ale bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje ładowanie tego filmu. Jeśli korzystasz z sieci VPN, spróbuj ją wyłączyć i ponownie załadować tę stronę.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jeśli to nie pomoże, nadal możesz oglądać ten film na YouTube, jednak bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." } diff --git a/build/windows/pages/duckplayer/locales/pt/duckplayer.json b/build/windows/pages/duckplayer/locales/pt/duckplayer.json index a5bfca188..2ff6fad9c 100644 --- a/build/windows/pages/duckplayer/locales/pt/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/pt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRO: ID de vídeo inválido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "O YouTube não permite que o Duck Player carregue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "O YouTube não permite que vejas este vídeo fora do YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "O YouTube está a bloquear o carregamento deste vídeo. Se estiveres a usar uma VPN, tenta desativá-la e recarregar esta página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se isto não funcionar, continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." } diff --git a/build/windows/pages/duckplayer/locales/ro/duckplayer.json b/build/windows/pages/duckplayer/locales/ro/duckplayer.json index bfafec70e..25ffac535 100644 --- a/build/windows/pages/duckplayer/locales/ro/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/ro/duckplayer.json @@ -32,6 +32,26 @@ "title" : "EROARE: ID video incorect", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nu permite Duck Player să încarce acest videoclip", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nu permite ca acest videoclip să fie vizionat în afara YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blochează încărcarea acestui videoclip. Dacă folosești un VPN, încearcă să-l dezactivezi și să reîncarci această pagină.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Dacă acest lucru nu funcționează, poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player oferă o experiență de vizionare fără perturbări, fără reclame personalizate și împiedică activitatea de vizionare să îți influențeze recomandările YouTube." } diff --git a/build/windows/pages/duckplayer/locales/ru/duckplayer.json b/build/windows/pages/duckplayer/locales/ru/duckplayer.json index 4bf5cc0c1..d1a3fc528 100644 --- a/build/windows/pages/duckplayer/locales/ru/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/ru/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ОШИБКА: Неверный идентификатор видео", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube не позволяет проигрывателю Duck Player загрузить это видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволяет смотреть это видео вне своей платформы.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Вы по-прежнему можете посмотреть этот ролик на YouTube, но уже без дополнительной защиты Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокирует загрузку этого видео. Если вы используете VPN, отключите ее и перезагрузите страницу.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Если это не даст результата, вы все равно сможете просмотреть это видео на YouTube, но без дополнительной защиты конфиденциальности, обеспечиваемой Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." } diff --git a/build/windows/pages/duckplayer/locales/sk/duckplayer.json b/build/windows/pages/duckplayer/locales/sk/duckplayer.json index 88a2cc3a5..a68cdeb89 100644 --- a/build/windows/pages/duckplayer/locales/sk/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/sk/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatný identifikátor videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovolí Duck Playeru načítať toto video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nepovoľuje, aby sa toto video pozeralo mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Toto video si môžeš pozrieť aj na YouTube, ale bez dodatočného súkromia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítanie tohto videa. Ak používaš sieť VPN, skús ju vypnúť a znova načítať túto stránku.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ak to nefunguje, môžeš si toto video pozrieť aj na YouTube, ale bez dodatočnej ochrany súkromia, ktorú poskytuje Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player poskytuje čisté zobrazenie bez personalizovaných reklám a zabraňuje tomu, aby aktivita pri sledovaní ovplyvňovala vaše odporúčania v službe YouTube." } diff --git a/build/windows/pages/duckplayer/locales/sl/duckplayer.json b/build/windows/pages/duckplayer/locales/sl/duckplayer.json index 7d4a89155..977830ef4 100644 --- a/build/windows/pages/duckplayer/locales/sl/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/sl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "NAPAKA: Neveljaven ID videoposnetka", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dovoli predvajalniku Duck Player naložiti tega videa", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dovoljuje, da si ta videoposnetek ogledate zunaj YouTuba.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Ta videoposnetek si lahko še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube preprečuje nalaganje tega videoposnetka. Če uporabljate omrežje VPN, ga poskusite izklopiti in znova naložite to stran.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Če to ne deluje, si lahko ta videoposnetek še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Predvajalnik Duck Player zagotavlja čisto izkušnjo gledanja brez prilagojenih oglasov in preprečuje, da bi dejavnost gledanja vplivala na vaša priporočila v YouTubu." } diff --git a/build/windows/pages/duckplayer/locales/sv/duckplayer.json b/build/windows/pages/duckplayer/locales/sv/duckplayer.json index cb5b75e4c..444e91750 100644 --- a/build/windows/pages/duckplayer/locales/sv/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/sv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEL: Ogiltigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube låter inte Duck Player läsa in den här videon", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillåter inte att den här videon ses utanför YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockerar den här videon från att läsas in. Om du använder ett VPN kan du prova stänga av det och läsa in sidan igen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Om det inte fungerar kan du fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ger en störningsfri visningsupplevelse utan personliga annonser och förhindrar att din tittaraktivitet påverkar YouTube-rekommendationer." } diff --git a/build/windows/pages/duckplayer/locales/tr/duckplayer.json b/build/windows/pages/duckplayer/locales/tr/duckplayer.json index c23cab2bb..e733fcaa6 100644 --- a/build/windows/pages/duckplayer/locales/tr/duckplayer.json +++ b/build/windows/pages/duckplayer/locales/tr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HATA: Geçersiz video kimliği", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube, Duck Player'ın bu videoyu yüklemesine izin vermiyor", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube, bu videonun YouTube dışında izlenmesine izin vermiyor.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Bu videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bu videonun yüklenmesini engelliyor. VPN kullanıyorsanız, VPN'i kapatıp bu sayfayı yeniden yüklemeyi deneyin.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Bu işe yaramazsa, videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player, kişiselleştirilmiş reklamlar olmadan temiz bir görüntüleme deneyimi sağlar ve görüntüleme etkinliğinin YouTube önerilerinizi etkilemesini önler." } diff --git a/special-pages/pages/duckplayer/app/components/Button.module.css b/special-pages/pages/duckplayer/app/components/Button.module.css index 19cc263cd..5d6845ee2 100644 --- a/special-pages/pages/duckplayer/app/components/Button.module.css +++ b/special-pages/pages/duckplayer/app/components/Button.module.css @@ -15,7 +15,7 @@ text-decoration: none; [data-layout="mobile"] & { - background-color: #2f2f2f; + background-color: rgba(255, 255, 255, 0.12); } } diff --git a/special-pages/pages/duckplayer/app/components/Components.jsx b/special-pages/pages/duckplayer/app/components/Components.jsx index 479650844..b866a9170 100644 --- a/special-pages/pages/duckplayer/app/components/Components.jsx +++ b/special-pages/pages/duckplayer/app/components/Components.jsx @@ -14,6 +14,7 @@ import { Settings } from '../settings.js'; import { EmbedSettings } from '../embed-settings.js'; import { SwitchBarDesktop } from './SwitchBarDesktop.jsx'; import { SwitchProvider } from '../providers/SwitchProvider.jsx'; +import { YouTubeError } from './YouTubeError'; export function Components() { const settings = new Settings({ @@ -81,6 +82,26 @@ export function Components() {
+ + + + + + + + +
+ + + + + + + + + +
+

inset=true (mobile)

@@ -90,7 +111,22 @@ export function Components() { +
+ + + + + + +
+ + + + + + +
diff --git a/special-pages/pages/duckplayer/app/components/Components.module.css b/special-pages/pages/duckplayer/app/components/Components.module.css index 9c0b4d88e..7aa2c776f 100644 --- a/special-pages/pages/duckplayer/app/components/Components.module.css +++ b/special-pages/pages/duckplayer/app/components/Components.module.css @@ -1,4 +1,5 @@ .main { + background-color: #000; color: white; max-width: 3840px; margin: 0 auto; diff --git a/special-pages/pages/duckplayer/app/components/DesktopApp.jsx b/special-pages/pages/duckplayer/app/components/DesktopApp.jsx index 3784e7f55..136a20b80 100644 --- a/special-pages/pages/duckplayer/app/components/DesktopApp.jsx +++ b/special-pages/pages/duckplayer/app/components/DesktopApp.jsx @@ -3,9 +3,11 @@ import styles from './DesktopApp.module.css'; import { InfoBar, InfoBarContainer } from './InfoBar.jsx'; import { PlayerContainer } from './PlayerContainer.jsx'; import { Player, PlayerError } from './Player.jsx'; +import { YouTubeError } from './YouTubeError'; import { useSettings } from '../providers/SettingsProvider.jsx'; import { createAppFeaturesFrom } from '../features/app.js'; import { HideInFocusMode } from './FocusMode.jsx'; +import { useYouTubeError } from '../providers/YouTubeErrorProvider'; /** * @param {object} props @@ -14,10 +16,12 @@ import { HideInFocusMode } from './FocusMode.jsx'; export function DesktopApp({ embed }) { const settings = useSettings(); const features = createAppFeaturesFrom(settings); + const youtubeError = useYouTubeError(); + return ( <> {features.focusMode()} -
+
@@ -29,11 +33,16 @@ export function DesktopApp({ embed }) { * @param {import("../embed-settings.js").EmbedSettings|null} props.embed */ function DesktopLayout({ embed }) { + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === 'enabled'; + return (
{embed === null && } - {embed !== null && } + {embed !== null && showCustomError && } + {embed !== null && !showCustomError && } diff --git a/special-pages/pages/duckplayer/app/components/MobileApp.jsx b/special-pages/pages/duckplayer/app/components/MobileApp.jsx index d8ec9b7c4..fa79e1fd5 100644 --- a/special-pages/pages/duckplayer/app/components/MobileApp.jsx +++ b/special-pages/pages/duckplayer/app/components/MobileApp.jsx @@ -2,6 +2,7 @@ import { h, Fragment } from 'preact'; import cn from 'classnames'; import styles from './MobileApp.module.css'; import { Player, PlayerError } from './Player.jsx'; +import { YouTubeError } from './YouTubeError'; import { usePlatformName, useSettings } from '../providers/SettingsProvider.jsx'; import { SwitchBarMobile } from './SwitchBarMobile.jsx'; import { MobileWordmark } from './Wordmark.jsx'; @@ -11,6 +12,7 @@ import { MobileButtons } from './MobileButtons.jsx'; import { OrientationProvider } from '../providers/OrientationProvider.jsx'; import { FocusMode } from './FocusMode.jsx'; import { useTelemetry } from '../types.js'; +import { useYouTubeError } from '../providers/YouTubeErrorProvider'; const DISABLED_HEIGHT = 450; @@ -21,12 +23,16 @@ const DISABLED_HEIGHT = 450; export function MobileApp({ embed }) { const settings = useSettings(); const telemetry = useTelemetry(); + const youtubeError = useYouTubeError(); + const features = createAppFeaturesFrom(settings); return ( <> - {features.focusMode()} + {!youtubeError && features.focusMode()} { + if (youtubeError) return; + if (orientation === 'portrait') { return FocusMode.enable(); } @@ -51,12 +57,17 @@ export function MobileApp({ embed }) { */ function MobileLayout({ embed }) { const platformName = usePlatformName(); + const youtubeError = useYouTubeError(); + const settings = useSettings(); + const showCustomError = youtubeError && settings.customError?.state === 'enabled'; + return ( -
+
{embed === null && } - {embed !== null && } + {embed !== null && showCustomError && } + {embed !== null && !showCustomError && }
diff --git a/special-pages/pages/duckplayer/app/components/MobileApp.module.css b/special-pages/pages/duckplayer/app/components/MobileApp.module.css index 8355572d0..0e75637b1 100644 --- a/special-pages/pages/duckplayer/app/components/MobileApp.module.css +++ b/special-pages/pages/duckplayer/app/components/MobileApp.module.css @@ -34,6 +34,8 @@ html[data-focus-mode="on"] .hideInFocus { --inner-radius: 12px; --logo-width: 157px; --inner-padding: 8px; + --mobile-buttons-padding: 8px; + position: relative; max-width: 100vh; margin: 0 auto; @@ -113,6 +115,16 @@ body:has([data-state="completed"] [aria-checked="true"]) .switch { height: 44px; } +.detachedControls { + grid-area: detached; + display: flex; + flex-flow: column; + gap: 8px; + padding: 8px; + background: #2f2f2f; + border-radius: 12px; +} + @media screen and (min-width: 425px) and (max-height: 600px) { .main { /* reset logo positioning */ @@ -222,3 +234,113 @@ body:has([data-state="completed"] [aria-checked="true"]) .switch { justify-content: end; } } + +/* Different layout for YouTube Errors on mobile */ +.main[data-youtube-error="true"] { + @media screen and (max-width: 599px) { + --bg-color: transparent; + --inner-padding: 4px; + + grid-template-areas: + 'logo' + 'gap3' + 'embed' + 'gap4' + 'switch' + 'buttons'; + grid-template-rows: + max-content + 16px + auto + 12px + max-content + max-content; + + & .embed { + background: #2f2f2f; + border-radius: var(--outer-radius); + padding: 4px; + } + + & .switch { + background: #2f2f2f; + padding: 8px 8px 0 8px; + height: 60px; + max-height: 60px; + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + + transition: all 0.3s; + } + + & .buttons { + background: #2f2f2f; + padding: 8px; + + transition: all 0.3s; + } + + &:has([data-state="completed"]) { + & .buttons { + border-radius: var(--outer-radius); + } + + & .switch { + background: transparent; + max-height: 0; + } + } + } + + /* Hide chrome on smaller screens */ + @media screen and (max-width: 599px) and (max-height: 599px) { + max-width: unset; + + grid-template-rows: + 0 + 0 + auto + 12px + 0 + max-content; + + & .logo, + & .switch { + display: none; + } + + & .buttons { + border-radius: var(--outer-radius); + } + } + + /* Show buttons on landscape */ + @media screen and (min-width: 600px) and (max-height: 450px) { + grid-template-areas: + 'embed' + 'buttons' + 'gap5'; + + grid-template-rows: + auto + max-content + 8px; + + & .buttons { + border-radius: var(--outer-radius); + display: block; + } + } + + /* Sticky buttons on very low heights */ + @media screen and (max-height: 320px) { + & .embed { + overflow-y: auto; + } + + & .buttons { + bottom: 0; + position: sticky; + } + } +} \ No newline at end of file diff --git a/special-pages/pages/duckplayer/app/components/Player.jsx b/special-pages/pages/duckplayer/app/components/Player.jsx index 1420dc43d..0815c7bb5 100644 --- a/special-pages/pages/duckplayer/app/components/Player.jsx +++ b/special-pages/pages/duckplayer/app/components/Player.jsx @@ -102,6 +102,7 @@ function useIframeEffects(src) { features.clickCapture(), features.titleCapture(), features.mouseCapture(), + features.errorDetection(), ]; /** diff --git a/special-pages/pages/duckplayer/app/components/SwitchBarMobile.module.css b/special-pages/pages/duckplayer/app/components/SwitchBarMobile.module.css index 685122b29..7d37733e1 100644 --- a/special-pages/pages/duckplayer/app/components/SwitchBarMobile.module.css +++ b/special-pages/pages/duckplayer/app/components/SwitchBarMobile.module.css @@ -1,7 +1,7 @@ .switchBar { display: grid; border-radius: 8px; - background: #2f2f2f; + background: rgba(255, 255, 255, 0.12); padding-inline: 16px; height: 100%; line-height: 1.1; diff --git a/special-pages/pages/duckplayer/app/components/Wordmark-mobile.module.css b/special-pages/pages/duckplayer/app/components/Wordmark-mobile.module.css index c80fa824c..dc50bd678 100644 --- a/special-pages/pages/duckplayer/app/components/Wordmark-mobile.module.css +++ b/special-pages/pages/duckplayer/app/components/Wordmark-mobile.module.css @@ -11,6 +11,13 @@ .logo { height: 100px; } + + /* TODO: Can this be moved somewhere else? */ + [data-youtube-error="true"] { + & .logo { + height: 44px; + } + } } .logoSvg img { display: block; diff --git a/special-pages/pages/duckplayer/app/components/YouTubeError.jsx b/special-pages/pages/duckplayer/app/components/YouTubeError.jsx new file mode 100644 index 000000000..677066749 --- /dev/null +++ b/special-pages/pages/duckplayer/app/components/YouTubeError.jsx @@ -0,0 +1,83 @@ +import { h } from 'preact'; +import cn from 'classnames'; +import { Settings } from '../settings'; +import { useTypedTranslation } from '../types.js'; + +import styles from './YouTubeError.module.css'; + +/** + * @typedef {import('../../types/duckplayer').YouTubeError} YouTubeError + * @typedef {import('preact').ComponentChild} ComponentChild + */ + +/** + * @param {YouTubeError} kind + * @returns {{heading: ComponentChild, messages: ComponentChild[], variant: 'list'|'inline'|'paragraphs'}} + */ +function useErrorStrings(kind) { + const { t } = useTypedTranslation(); + + switch (kind) { + case 'sign-in-required': + return { + heading: t('blockedVideoErrorHeading'), + messages: [t('signInRequiredErrorMessage1'), t('signInRequiredErrorMessage2')], + variant: 'paragraphs', + }; + default: + return { + heading: t('blockedVideoErrorHeading'), + messages: [t('blockedVideoErrorMessage1'), t('blockedVideoErrorMessage2')], + variant: 'paragraphs', + }; + } +} + +/** + * @param {object} props + * @param {YouTubeError} props.kind + * @param {Settings['layout']} props.layout + */ +export function YouTubeError({ kind, layout }) { + const { heading, messages, variant } = useErrorStrings(kind); + const classes = cn(styles.error, { + [styles.desktop]: layout === 'desktop', + [styles.mobile]: layout === 'mobile', + }); + + return ( +
+
+ + +
+

{heading}

+ + {messages && variant === 'inline' && ( +

+ {messages.map((item) => ( + {item} + ))} +

+ )} + + {messages && variant === 'paragraphs' && ( +
+ {messages.map((item) => ( +

{item}

+ ))} +
+ )} + + {messages && variant === 'list' && ( +
    + {messages.map((item) => ( +
  • {item}
  • + ))} +
+ )} +
+
+
+ ); +} diff --git a/special-pages/pages/duckplayer/app/components/YouTubeError.module.css b/special-pages/pages/duckplayer/app/components/YouTubeError.module.css new file mode 100644 index 000000000..c040a7cf1 --- /dev/null +++ b/special-pages/pages/duckplayer/app/components/YouTubeError.module.css @@ -0,0 +1,134 @@ +.error { + align-items: center; + background: rgba(0, 0, 0, 0.6); + display: grid; + height: 100%; + justify-items: center; +} + +.error.desktop { + height: var(--frame-height); + overflow: hidden; + position: relative; + z-index: 1; +} + +.error.mobile { + border-radius: var(--inner-radius); + height: 100%; + overflow: auto; + + /* Prevents automatic text resizing */ + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + + @media screen and (min-width: 600px) and (min-height: 600px) { + aspect-ratio: 16 / 9; + } +} + +.desktop { + border-top-left-radius: var(--outer-radius); + border-top-right-radius: var(--outer-radius); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.container { + column-gap: 24px; + display: flex; + flex-flow: row; + margin: 0; + max-width: 680px; + padding: 0 40px; + row-gap: 4px; +} + +.mobile .container { + flex-flow: column; + padding: 0 24px; + + @media screen and (min-height: 320px) { + margin: 16px 0; + } + + @media screen and (min-width: 375px) and (min-height: 400px) { + margin: 36px 0; + } +} + +.content { + display: flex; + flex-direction: column; + gap: 4px; + margin: 16px 0; + + @media screen and (min-width: 600px) { + margin: 24px 0; + } +} + + +.icon { + align-self: center; + display: flex; + justify-content: center; + + &::before { + content: ' '; + display: block; + background: url('../img/warning-96.data.svg') no-repeat; + height: 48px; + width: 48px; + } + + @media screen and (max-width: 320px) { + display: none; + } + + @media screen and (min-width: 600px) and (min-height: 600px) { + justify-content: start; + + &::before { + background-image: url('../img/warning-128.data.svg'); + height: 96px; + width: 128px; + } + } +} + +.heading { + color: #fff; + font-size: 20px; + font-weight: 700; + line-height: calc(24 / 20); + margin: 0; +} + +.messages { + color: #ccc; + font-size: 16px; + line-height: calc(24 / 16); +} + +div.messages { + display: flex; + flex-direction: column; + gap: 24px; + + & p { + margin: 0; + } +} + +p.messages { + margin: 0; +} + +ul.messages { + li { + list-style: disc; + margin-left: 24px; + } +} + diff --git a/special-pages/pages/duckplayer/app/features/error-detection.js b/special-pages/pages/duckplayer/app/features/error-detection.js new file mode 100644 index 000000000..6ce3ddbdd --- /dev/null +++ b/special-pages/pages/duckplayer/app/features/error-detection.js @@ -0,0 +1,144 @@ +import { YOUTUBE_ERROR_EVENT, YOUTUBE_ERRORS } from '../providers/YouTubeErrorProvider'; + +/** + * @typedef {import("./iframe").IframeFeature} IframeFeature + * @typedef {import('../../types/duckplayer').YouTubeError} YouTubeError + * @typedef {import('../../types/duckplayer').DuckPlayerPageSettings['customError']} CustomErrorOptions + */ + +/** + * Detects YouTube errors based on DOM queries + * + * @implements IframeFeature + */ +export class ErrorDetection { + /** @type {HTMLIFrameElement} */ + iframe; + + /** @type {CustomErrorOptions} */ + options; + + /** + * @param {CustomErrorOptions} options + */ + constructor(options) { + this.options = options; + } + + /** + * @param {HTMLIFrameElement} iframe + */ + iframeDidLoad(iframe) { + this.iframe = iframe; + + if (!this.options || !this.options.signInRequiredSelector) { + console.log('Missing Custom Error options'); + return null; + } + + const documentBody = iframe.contentWindow?.document?.body; + if (documentBody) { + // Check if iframe already contains error + if (this.checkForError(documentBody)) { + const error = this.getErrorType(); + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + + return null; + } + + // Create a MutationObserver instance + const observer = new MutationObserver(this.handleMutation.bind(this)); + + // Start observing the iframe's document for changes + observer.observe(documentBody, { + childList: true, + subtree: true, // Observe all descendants of the body + }); + + return () => { + observer.disconnect(); + }; + } + + return null; + } + + /** + * Mutation handler that checks new nodes for error states + * + * @type {MutationCallback} + */ + handleMutation(mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (this.checkForError(node)) { + console.log('A node with an error has been added to the document:', node); + const error = this.getErrorType(); + + window.dispatchEvent(new CustomEvent(YOUTUBE_ERROR_EVENT, { detail: { error } })); + } + }); + } + } + } + + /** + * Attempts to detect the type of error in the YouTube embed iframe + * @returns {YouTubeError} + */ + getErrorType() { + const iframeWindow = /** @type {Window & { ytcfg: object }} */ (this.iframe.contentWindow); + let playerResponse; + + try { + playerResponse = JSON.parse(iframeWindow.ytcfg?.get('PLAYER_VARS')?.embedded_player_response); + } catch (e) { + console.log('Could not parse player response', e); + } + + if (typeof playerResponse === 'object') { + const { + previewPlayabilityStatus: { desktopLegacyAgeGateReason, status }, + } = playerResponse; + + // 1. Check for UNPLAYABLE status + if (status === 'UNPLAYABLE') { + // 1.1. Check for presence of desktopLegacyAgeGateReason + if (desktopLegacyAgeGateReason === 1) { + return YOUTUBE_ERRORS.ageRestricted; + } + + // 1.2. Fall back to embed not allowed error + return YOUTUBE_ERRORS.noEmbed; + } + + // 2. Check for sign-in support link + try { + if (this.options?.signInRequiredSelector && !!iframeWindow.document.querySelector(this.options.signInRequiredSelector)) { + return YOUTUBE_ERRORS.signInRequired; + } + } catch (e) { + console.log('Sign-in required query failed', e); + } + } + + // 3. Fall back to unknown error + return YOUTUBE_ERRORS.unknown; + } + + /** + * Analyses a node and its children to determine if it contains an error state + * + * @param {Node} [node] + */ + checkForError(node) { + if (node?.nodeType === Node.ELEMENT_NODE) { + const element = /** @type {HTMLElement} */ (node); + // Check if element has the error class or contains any children with that class + return element.classList.contains('ytp-error') || !!element.querySelector('ytp-error'); + } + + return false; + } +} diff --git a/special-pages/pages/duckplayer/app/features/iframe.js b/special-pages/pages/duckplayer/app/features/iframe.js index b252724b2..c9c0fb0ef 100644 --- a/special-pages/pages/duckplayer/app/features/iframe.js +++ b/special-pages/pages/duckplayer/app/features/iframe.js @@ -3,6 +3,7 @@ import { AutoFocus } from './autofocus.js'; import { ClickCapture } from './click-capture.js'; import { TitleCapture } from './title-capture.js'; import { MouseCapture } from './mouse-capture.js'; +import { ErrorDetection } from './error-detection.js'; /** * Represents an individual piece of functionality in the iframe. @@ -74,5 +75,11 @@ export function createIframeFeatures(settings) { mouseCapture: () => { return new MouseCapture(); }, + /** + * @return {IframeFeature} + */ + errorDetection: () => { + return new ErrorDetection(settings.customError); + }, }; } diff --git a/special-pages/pages/duckplayer/app/img/warning-128.data.svg b/special-pages/pages/duckplayer/app/img/warning-128.data.svg new file mode 100644 index 000000000..b0392efdc --- /dev/null +++ b/special-pages/pages/duckplayer/app/img/warning-128.data.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/special-pages/pages/duckplayer/app/img/warning-96.data.svg b/special-pages/pages/duckplayer/app/img/warning-96.data.svg new file mode 100644 index 000000000..af6bc6fbe --- /dev/null +++ b/special-pages/pages/duckplayer/app/img/warning-96.data.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/special-pages/pages/duckplayer/app/index.js b/special-pages/pages/duckplayer/app/index.js index da81d0f8b..a0722feff 100644 --- a/special-pages/pages/duckplayer/app/index.js +++ b/special-pages/pages/duckplayer/app/index.js @@ -14,6 +14,9 @@ import { Fallback } from '../../../shared/components/Fallback/Fallback.jsx'; import { Components } from './components/Components.jsx'; import { MobileApp } from './components/MobileApp.jsx'; import { DesktopApp } from './components/DesktopApp.jsx'; +import { YouTubeErrorProvider } from './providers/YouTubeErrorProvider'; + +/** @typedef {import('../types/duckplayer').YouTubeError} YouTubeError */ /** * @param {import("../src/index.js").DuckplayerPage} messaging @@ -55,7 +58,11 @@ export async function init(messaging, telemetry, baseEnvironment) { .withFeatureState('pip', init.settings.pip) .withFeatureState('autoplay', init.settings.autoplay) .withFeatureState('focusMode', init.settings.focusMode) - .withDisabledFocusMode(baseEnvironment.urlParams.get('focusMode')); + .withFeatureState('customError', init.settings.customError) + .withDisabledFocusMode(baseEnvironment.urlParams.get('focusMode')) + .withCustomError(baseEnvironment.urlParams.get('customError')); + + const initialYouTubeError = /** @type {YouTubeError} */ (baseEnvironment.urlParams.get('youtubeError')); console.log(settings); @@ -79,27 +86,29 @@ export async function init(messaging, telemetry, baseEnvironment) { - - {settings.layout === 'desktop' && ( - - - - )} - {settings.layout === 'mobile' && ( - - - - )} - - + + + {settings.layout === 'desktop' && ( + + + + )} + {settings.layout === 'mobile' && ( + + + + )} + + + diff --git a/special-pages/pages/duckplayer/app/providers/YouTubeErrorProvider.jsx b/special-pages/pages/duckplayer/app/providers/YouTubeErrorProvider.jsx new file mode 100644 index 000000000..a91916685 --- /dev/null +++ b/special-pages/pages/duckplayer/app/providers/YouTubeErrorProvider.jsx @@ -0,0 +1,75 @@ +import { useContext, useState } from 'preact/hooks'; +import { h, createContext } from 'preact'; +import { useEffect } from 'preact/hooks'; +import { useMessaging } from '../types'; +import { usePlatformName } from './SettingsProvider'; +import { useSetFocusMode } from '../components/FocusMode'; + +export const YOUTUBE_ERROR_EVENT = 'ddg-duckplayer-youtube-error'; + +/** + * @typedef {import('../../types/duckplayer').YouTubeError} YouTubeError + */ + +/** @type {Record} */ +export const YOUTUBE_ERRORS = { + ageRestricted: 'age-restricted', + signInRequired: 'sign-in-required', + noEmbed: 'no-embed', + unknown: 'unknown', +}; + +/** @type {YouTubeError[]} */ +export const YOUTUBE_ERROR_IDS = Object.values(YOUTUBE_ERRORS); + +const YouTubeErrorContext = createContext({ + /** @type {YouTubeError|null} */ + error: null, +}); + +/** + * @param {object} props + * @param {YouTubeError|null} [props.initial=null] + * @param {import("preact").ComponentChild} props.children + */ +export function YouTubeErrorProvider({ initial = null, children }) { + // initial state + let initialError = null; + if (initial && YOUTUBE_ERROR_IDS.includes(initial)) { + initialError = initial; + } + const [error, setError] = useState(initialError); + + const messaging = useMessaging(); + const platformName = usePlatformName(); + const setFocusMode = useSetFocusMode(); + + // listen for updates + useEffect(() => { + /** @type {(event: CustomEvent) => void} */ + const errorEventHandler = (event) => { + const eventError = event.detail?.error; + if (YOUTUBE_ERROR_IDS.includes(eventError) || eventError === null) { + if (eventError && eventError !== error) { + setFocusMode('paused'); + if (platformName === 'macos' || platformName === 'ios') { + messaging.reportYouTubeError({ error: eventError }); + } + } else { + setFocusMode('enabled'); + } + setError(eventError); + } + }; + + window.addEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + + return () => window.removeEventListener(YOUTUBE_ERROR_EVENT, errorEventHandler); + }, []); + + return {children}; +} + +export function useYouTubeError() { + return useContext(YouTubeErrorContext).error; +} diff --git a/special-pages/pages/duckplayer/app/settings.js b/special-pages/pages/duckplayer/app/settings.js index f0f52adc7..46f88dc78 100644 --- a/special-pages/pages/duckplayer/app/settings.js +++ b/special-pages/pages/duckplayer/app/settings.js @@ -1,3 +1,5 @@ +const DEFAULT_SIGN_IN_REQURED_HREF = '[href*="//support.google.com/youtube/answer/3037019"]'; + export class Settings { /** * @param {object} params @@ -5,17 +7,20 @@ export class Settings { * @param {{state: 'enabled' | 'disabled'}} [params.pip] * @param {{state: 'enabled' | 'disabled'}} [params.autoplay] * @param {{state: 'enabled' | 'disabled'}} [params.focusMode] + * @param {import("../types/duckplayer.js").InitialSetupResponse['settings']['customError']} [params.customError] */ constructor({ platform = { name: 'macos' }, pip = { state: 'disabled' }, autoplay = { state: 'enabled' }, focusMode = { state: 'enabled' }, + customError = { state: 'disabled', signInRequiredSelector: '' }, }) { this.platform = platform; this.pip = pip; this.autoplay = autoplay; this.focusMode = focusMode; + this.customError = customError; } /** @@ -26,7 +31,7 @@ export class Settings { withFeatureState(named, settings) { if (!settings) return this; /** @type {(keyof import("../types/duckplayer.js").DuckPlayerPageSettings)[]} */ - const valid = ['pip', 'autoplay', 'focusMode']; + const valid = ['pip', 'autoplay', 'focusMode', 'customError']; if (!valid.includes(named)) { console.warn(`Excluding invalid feature key ${named}`); return this; @@ -68,6 +73,31 @@ export class Settings { return this; } + /** + * @param {string|null|undefined} newState + * @return {Settings} + */ + withCustomError(newState) { + if (newState === 'disabled') { + return new Settings({ + ...this, + customError: { state: 'disabled' }, + }); + } + + if (newState === 'enabled') { + return new Settings({ + ...this, + customError: { + state: 'enabled', + signOnRequiredSelector: DEFAULT_SIGN_IN_REQURED_HREF, + }, + }); + } + + return this; + } + /** * @return {string} */ diff --git a/special-pages/pages/duckplayer/integration-tests/duck-player.js b/special-pages/pages/duckplayer/integration-tests/duck-player.js index 4ea48c3ee..7cd569e41 100644 --- a/special-pages/pages/duckplayer/integration-tests/duck-player.js +++ b/special-pages/pages/duckplayer/integration-tests/duck-player.js @@ -22,6 +22,26 @@ const html = {
+`, + signInRequired: `${MOCK_VIDEO_TITLE} + + + `, }; @@ -156,6 +176,14 @@ export class DuckPlayerPage { }); } + if (urlParams.get('videoID') === 'SIGN_IN_REQUIRED') { + return request.fulfill({ + status: 200, + body: html.signInRequired, + contentType: 'text/html', + }); + } + const mp4VideoPlaceholderAsDataURI = 'data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAwFtZGF0AAACogYF//+b3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE1MiByMjg1NCBlMjA5YTFjIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNyAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzowMTMzIHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02MyBsb29rYWhlYWRfdGhyZWFkcz0yIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD1xLTIgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtleWludD0yNTAga2V5aW50X21pbj0yNSBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmM9bG9va2FoZWFkIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCB2YnY9MCBjbG9zZWRfZ29wPTAgY3V0X3Rocm91Z2g9MCAnbm8tZGlndHMuanBnLTFgcC1mbHWinS3SlB8AP0AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABSAAAAAAAAAAAAAAAAAAABBZHJ0AAAAAAAAAA=='; return request.fulfill({ @@ -214,6 +242,16 @@ export class DuckPlayerPage { await this.openPage(params); } + /** + * @param {import('../types/duckplayer.ts').YouTubeError} [youtubeError] + * @param {string} [videoID] + * @returns {Promise} + */ + async openWithYouTubeError(youtubeError = 'unknown', videoID = 'e90eWYPNtJ8') { + const params = new URLSearchParams({ youtubeError, videoID, customError: 'enabled', focusMode: 'disabled' }); + await this.openPage(params); + } + async openWithException() { const params = new URLSearchParams({ willThrow: String(true) }); await this.openPage(params); @@ -306,8 +344,12 @@ export class DuckPlayerPage { await expect(this.page.locator('iframe')).toHaveAttribute('src', expected); } - async hasShownErrorMessage() { - await expect(this.page.getByText('ERROR: Invalid video id')).toBeVisible(); + /** + * + * @param {string} text + */ + async hasShownErrorMessage(text = 'ERROR: Invalid video id') { + await expect(this.page.getByText(text)).toBeVisible(); } async hasNotAddedIframe() { @@ -396,6 +438,40 @@ export class DuckPlayerPage { }); } + async opensDuckPlayerYouTubeLinkFromError({ videoID = 'UNSUPPORTED' }) { + const action = () => this.page.getByRole('button', { name: 'Watch on YouTube' }).click(); + await this.build.switch({ + windows: async () => { + const failure = new Promise((resolve) => { + this.page.context().on('requestfailed', (f) => { + resolve(f.url()); + }); + }); + await action(); + expect(await failure).toEqual(`duck://player/openInYoutube?v=${videoID}`); + }, + apple: async () => { + if (this.platform.name === 'ios') { + // todo: why does this not work on ios?? + await action(); + return; + } + await action(); + await this.page.waitForURL(`https://www.youtube.com/watch?v=${videoID}`); + }, + android: async () => { + // const failure = new Promise(resolve => { + // this.page.context().on('requestfailed', f => { + // resolve(f.url()) + // }) + // }) + // todo: why does this not work on android? + await action(); + // expect(await failure).toEqual(`duck://player/openInYoutube?v=${videoID}`) + }, + }); + } + /** * @return {Promise} */ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js index 41485178d..1573a015d 100644 --- a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js +++ b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js @@ -26,6 +26,18 @@ test.describe('screenshots @screenshots', () => { await duckplayer.hasShownErrorMessage(); await expect(page).toHaveScreenshot('error-layout.png', { maxDiffPixels: 20 }); }); + test('youtube generic error', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('unknown'); + await duckplayer.hasShownErrorMessage('YouTube won’t let Duck Player load this video'); + await expect(page).toHaveScreenshot('youtube-error-unknown.png', { maxDiffPixels: 20 }); + }); + test('youtube sign-in error', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('sign-in-required'); + await duckplayer.hasShownErrorMessage('YouTube won’t let Duck Player load this video'); + await expect(page).toHaveScreenshot('youtube-error-sign-in-required.png', { maxDiffPixels: 20 }); + }); test('tooltip shown on hover', async ({ page }, workerInfo) => { test.skip(isMobile(workerInfo)); const duckplayer = DuckPlayerPage.create(page, workerInfo); diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-android-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-android-darwin.png new file mode 100644 index 000000000..2c56c28fc Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-android-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-android-landscape-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-android-landscape-darwin.png new file mode 100644 index 000000000..d659b2d5c Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-android-landscape-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-ios-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-ios-darwin.png new file mode 100644 index 000000000..556c4c82f Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-ios-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-macos-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-macos-darwin.png new file mode 100644 index 000000000..a574576e0 Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-macos-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-windows-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-windows-darwin.png new file mode 100644 index 000000000..7677fb326 Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-sign-in-required-windows-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-android-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-android-darwin.png new file mode 100644 index 000000000..6f88330fb Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-android-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-android-landscape-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-android-landscape-darwin.png new file mode 100644 index 000000000..038756cac Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-android-landscape-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-ios-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-ios-darwin.png new file mode 100644 index 000000000..9ba77fa6f Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-ios-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-macos-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-macos-darwin.png new file mode 100644 index 000000000..7ae4e54ee Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-macos-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-windows-darwin.png b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-windows-darwin.png new file mode 100644 index 000000000..0a93bd371 Binary files /dev/null and b/special-pages/pages/duckplayer/integration-tests/duckplayer-screenshots.spec.js-snapshots/youtube-error-unknown-windows-darwin.png differ diff --git a/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js b/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js index d2aa8456d..d7b8409d8 100644 --- a/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js +++ b/special-pages/pages/duckplayer/integration-tests/duckplayer.spec.js @@ -96,6 +96,53 @@ test.describe('duckplayer iframe', () => { }); }); +test.describe('duckplayer custom error', () => { + test('shows custom error screen for videos that require sign-in', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('sign-in-required', 'e90eWYPNtJ8'); + await duckplayer.hasShownErrorMessage('YouTube won’t let Duck Player load this video'); + await duckplayer.hasShownErrorMessage('If you’re using a VPN, try turning it off'); + }); + test('supports "watch on youtube" for videos that require sign-in', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('sign-in-required', 'e90eWYPNtJ8'); + await duckplayer.opensDuckPlayerYouTubeLinkFromError({ videoID: 'e90eWYPNtJ8' }); + }); + test('shows custom error screen for videos that are age-restricted', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('age-restricted', 'e90eWYPNtJ8'); + await duckplayer.hasShownErrorMessage('YouTube won’t let Duck Player load this video'); + await duckplayer.hasShownErrorMessage('YouTube doesn’t allow this video to be viewed'); + }); + test('supports "watch on youtube" for videos that are age-restricted', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('age-restricted', 'e90eWYPNtJ8'); + await duckplayer.opensDuckPlayerYouTubeLinkFromError({ videoID: 'e90eWYPNtJ8' }); + }); + test('shows custom error screen for videos that can’t be embedded', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('no-embed', 'e90eWYPNtJ8'); + await duckplayer.hasShownErrorMessage('YouTube won’t let Duck Player load this video'); + await duckplayer.hasShownErrorMessage('YouTube doesn’t allow this video to be viewed'); + }); + test('supports "watch on youtube" for videos that can’t be embedded', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('no-embed', 'e90eWYPNtJ8'); + await duckplayer.opensDuckPlayerYouTubeLinkFromError({ videoID: 'e90eWYPNtJ8' }); + }); + test('shows custom error screen for videos with unknown errors', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('unknown', 'e90eWYPNtJ8'); + await duckplayer.hasShownErrorMessage('YouTube won’t let Duck Player load this video'); + await duckplayer.hasShownErrorMessage('YouTube doesn’t allow this video to be viewed'); + }); + test('supports "watch on youtube" for videos with unknown errors', async ({ page }, workerInfo) => { + const duckplayer = DuckPlayerPage.create(page, workerInfo); + await duckplayer.openWithYouTubeError('unknown', 'e90eWYPNtJ8'); + await duckplayer.opensDuckPlayerYouTubeLinkFromError({ videoID: 'e90eWYPNtJ8' }); + }); +}); + test.describe('duckplayer toolbar', () => { test('hides toolbar based on user activity', async ({ page }, workerInfo) => { test.skip(isMobile(workerInfo)); diff --git a/special-pages/pages/duckplayer/messages/initialSetup.response.json b/special-pages/pages/duckplayer/messages/initialSetup.response.json index 75a99ca5c..c222d0c05 100644 --- a/special-pages/pages/duckplayer/messages/initialSetup.response.json +++ b/special-pages/pages/duckplayer/messages/initialSetup.response.json @@ -38,6 +38,21 @@ "enum": ["enabled", "disabled"] } } + }, + "customError": { + "type": "object", + "description": "Configures a custom error message for YouTube errors", + "required": ["state", "signInRequiredSelector"], + "properties": { + "state": { + "type": "string", + "enum": ["enabled", "disabled"] + }, + "signInRequiredSelector": { + "description": "A selector that, when not empty, indicates a sign-in required error", + "type": "string" + } + } } } }, diff --git a/special-pages/pages/duckplayer/messages/reportYouTubeError.notify.json b/special-pages/pages/duckplayer/messages/reportYouTubeError.notify.json new file mode 100644 index 000000000..f7b5b4c4b --- /dev/null +++ b/special-pages/pages/duckplayer/messages/reportYouTubeError.notify.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["error"], + "properties": { + "error": { "$ref": "youtubeError.shared.json"} + } +} diff --git a/special-pages/pages/duckplayer/messages/youtubeError.shared.json b/special-pages/pages/duckplayer/messages/youtubeError.shared.json new file mode 100644 index 000000000..4616843e4 --- /dev/null +++ b/special-pages/pages/duckplayer/messages/youtubeError.shared.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "YouTubeError", + "type": "string", + "enum": ["age-restricted", "sign-in-required", "no-embed", "unknown"] +} diff --git a/special-pages/pages/duckplayer/public/locales/bg/duckplayer.json b/special-pages/pages/duckplayer/public/locales/bg/duckplayer.json index d7f3d20ef..c807a2f78 100644 --- a/special-pages/pages/duckplayer/public/locales/bg/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/bg/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ГРЕШКА: невалиден идентификатор на видеоклипа", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube няма да позволи на Duck Player да зареди това видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволява това видео да бъде гледано извън YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Все пак можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокира зареждането на това видео. Ако използвате VPN, опитайте да го изключите и презаредете тази страница.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ако това не свърши работа, можете да гледате това видео в YouTube, но без допълнителната поверителност на Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player осигурява чисто изживяване без персонализирани реклами в YouTube и предотвратява влиянието на вече гледаните видеоклипове върху препоръките на YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/cs/duckplayer.json b/special-pages/pages/duckplayer/public/locales/cs/duckplayer.json index 8d24c5726..690569a5d 100644 --- a/special-pages/pages/duckplayer/public/locales/cs/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/cs/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatné ID videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovoluje přehrávači Duck Player načíst tohle video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nedovoluje spuštění videa mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Na tohle video se můžeš pořád podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítání tohohle videa. Pokud používáš VPN, zkus ji vypnout a stránku znovu načíst.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Pokud to nefunguje, můžeš se na video podívat na YouTube, ale bez ochrany soukromí, jakou nabízí Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Přehrávač Duck Player nabízí sledování v minimalistickém prostředí bez personalizovaných reklam a brání tomu, aby sledovaná videa ovlivňovala tvoje doporučení na YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/da/duckplayer.json b/special-pages/pages/duckplayer/public/locales/da/duckplayer.json index f99ab298e..aae6004c2 100644 --- a/special-pages/pages/duckplayer/public/locales/da/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/da/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEJL: Ugyldigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube vil ikke lade Duck Player indlæse denne video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillader ikke, at denne video vises uden for YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokerer for, at denne video kan indlæses. Hvis du bruger en VPN, så prøv at slå den fra og genindlæse denne side.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis dette ikke virker, kan du stadig se denne video på YouTube, men uden den ekstra fortrolighed, som Duck Player giver.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player giver en ren seeroplevelse uden målrettede annoncer og forhindrer, at visningsaktivitet påvirker dine YouTube-anbefalinger." } diff --git a/special-pages/pages/duckplayer/public/locales/de/duckplayer.json b/special-pages/pages/duckplayer/public/locales/de/duckplayer.json index 0ddca103a..abd163119 100644 --- a/special-pages/pages/duckplayer/public/locales/de/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/de/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEHLER: Ungültige Video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lässt den Duck Player dieses Video nicht laden", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube erlaubt nicht, dass dieses Video außerhalb von YouTube angesehen wird.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kannst dieses Video auf YouTube ansehen, aber ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockiert das Laden dieses Videos. Falls du ein VPN benutzt, deaktiviere es und lade diese Seite neu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Falls das nicht funktioniert, kannst du das Video dennoch auf YouTube ansehen, jedoch ohne die zusätzliche Privatsphäre des Duck Players.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." } diff --git a/special-pages/pages/duckplayer/public/locales/el/duckplayer.json b/special-pages/pages/duckplayer/public/locales/el/duckplayer.json index d00195f5e..2d8fa43c5 100644 --- a/special-pages/pages/duckplayer/public/locales/el/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/el/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ΣΦΑΛΜΑ: Μη έγκυρο αναγνωριστικό βίντεο", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "Το YouTube δεν θα αφήσει το Duck Player να φορτώσει το βίντεο αυτό", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Το YouTube δεν επιτρέπει την προβολή αυτού του βίντεο εκτός YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Μπορείτε ακόμα να παρακολουθήσετε αυτό το βίντεο στο YouTube, αλλά χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "Το YouTube μπλοκάρει τη φόρτωση αυτού του βίντεο. Εάν χρησιμοποιείτε VPN, δοκιμάστε να το απενεργοποιήσετε και να φορτώσετε εκ νέου αυτήν τη σελίδα.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Εάν δεν λειτουργήσει αυτό, μπορείτε να παρακολουθήσετε αυτό το βίντεο στο YouTube, ωστόσο χωρίς την πρόσθετη ιδιωτικότητα του Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Το Duck Player παρέχει μια καθαρή εμπειρία προβολής χωρίς εξατομικευμένες διαφημίσεις, ενώ εμποδίζει τη δραστηριότητα προβολής να επηρεάσει τις συστάσεις που θα λαμβάνετε στο YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/en/duckplayer.json b/special-pages/pages/duckplayer/public/locales/en/duckplayer.json index c2b5683b9..fe94917c4 100644 --- a/special-pages/pages/duckplayer/public/locales/en/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/en/duckplayer.json @@ -33,6 +33,26 @@ "title": "ERROR: Invalid video id", "note": "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading": { + "title": "YouTube won’t let Duck Player load this video", + "note": "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1": { + "title": "YouTube doesn’t allow this video to be viewed outside of YouTube.", + "note": "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2": { + "title": "You can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1": { + "title": "YouTube is blocking this video from loading. If you’re using a VPN, try turning it off and reloading this page.", + "note": "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2": { + "title": "If this doesn’t work, you can still watch this video on YouTube, but without the added privacy of Duck Player.", + "note": "More troubleshooting tips for this specific error" + }, "tooltipInfo": { "title": "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } diff --git a/special-pages/pages/duckplayer/public/locales/es/duckplayer.json b/special-pages/pages/duckplayer/public/locales/es/duckplayer.json index 1b5d8b958..f5af0d046 100644 --- a/special-pages/pages/duckplayer/public/locales/es/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/es/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERROR: ID de vídeo no válida", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube no permite que Duck Player cargue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube no permite que este vídeo se vea fuera de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional que ofrece Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube está bloqueando la carga de este vídeo. Si estás usando una VPN, intenta desactivarla y volver a cargar la página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si esto no funciona, sigues pudiendo ver este vídeo en YouTube, pero sin la privacidad adicional de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/et/duckplayer.json b/special-pages/pages/duckplayer/public/locales/et/duckplayer.json index c9863f4f5..70b30efee 100644 --- a/special-pages/pages/duckplayer/public/locales/et/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/et/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIGA: vale video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei luba Duck Playeril seda videot laadida", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei luba seda videot väljaspool YouTube'i vaadata.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Saate seda videot endiselt YouTube'is vaadata, kuid ilma Duck Player'i lisatud privaatsuseta.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokeerib selle video laadimise. Kui kasutate VPN-i, proovige see välja lülitada ning leht uuesti laadida.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Kui see ei aita, saate seda videot ikkagi YouTube'is vaadata, kuid ilma Duck Playeri lisatud privaatsuseta.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pakub isikupärastatud reklaamidest vaba vaatamiskogemust ja takistab, et vaatamisaktiivsus mõjutaks sinu YouTube'i soovitusi." } diff --git a/special-pages/pages/duckplayer/public/locales/fi/duckplayer.json b/special-pages/pages/duckplayer/public/locales/fi/duckplayer.json index e73022b8f..4c830a739 100644 --- a/special-pages/pages/duckplayer/public/locales/fi/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/fi/duckplayer.json @@ -32,6 +32,26 @@ "title" : "VIRHE: virheellinen videotunnus", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ei salli Duck Playerin ladata tätä videota.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ei salli tämän videon katsomista YouTuben ulkopuolella.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Voit yhä katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube estää tämän videon latautumisen. Jos käytät VPN:ää, kytke se pois päältä ja lataa tämä sivu uudelleen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jos tämä ei toimi, voit silti katsoa tämän videon YouTubessa, mutta ilman Duck Playerin tarjoamaa ylimääräistä tietosuojaa.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tarjoaa puhtaan katselukokemuksen ilman kohdennettuja mainoksia ja estää katseluhistoriaa vaikuttamasta YouTube-suosituksiisi." } diff --git a/special-pages/pages/duckplayer/public/locales/fr/duckplayer.json b/special-pages/pages/duckplayer/public/locales/fr/duckplayer.json index 716f0c071..12eb0ac92 100644 --- a/special-pages/pages/duckplayer/public/locales/fr/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/fr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERREUR : identifiant vidéo non valide", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne permet pas à Duck Player de charger cette vidéo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube n'autorise pas le visionnage de cette vidéo en dehors de YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloque le chargement de cette vidéo. Si vous utilisez un VPN, essayez de le désactiver et de recharger cette page.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Si cela ne fonctionne pas, vous pouvez toujours regarder cette vidéo sur YouTube, mais sans la confidentialité renforcée de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/hr/duckplayer.json b/special-pages/pages/duckplayer/public/locales/hr/duckplayer.json index 3f0e8aeae..48fdbf997 100644 --- a/special-pages/pages/duckplayer/public/locales/hr/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/hr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "POGREŠKA: Nevažeći ID videozapisa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dopušta Duck Playeru da učita ovaj videozapis.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dopušta da se ovaj videozapis gleda izvan YouTubea.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Još uvijek možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokira učitavanje ovog videozapisa. Ako koristiš VPN, pokušaj ga isključiti i ponovno učitati ovu stranicu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ako to ne uspije, i dalje možeš gledati ovaj videozapis na YouTubeu, ali bez dodatne privatnosti Duck Playera.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player pruža čisti doživljaj gledanja bez personaliziranih oglasa i sprječava da aktivnosti gledanja utječu na tvoje preporuke na YouTubeu." } diff --git a/special-pages/pages/duckplayer/public/locales/hu/duckplayer.json b/special-pages/pages/duckplayer/public/locales/hu/duckplayer.json index 3bbe06210..c8faf475f 100644 --- a/special-pages/pages/duckplayer/public/locales/hu/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/hu/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HIBA: Érvénytelen videoazonosító", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "A YouTube nem engedi, hogy a Duck Player betöltse ezt a videót", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "A YouTube nem engedi, hogy ezt a videót a YouTube-on kívül nézd meg.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Megnézheted a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "A YouTube blokkolja ennek a videónak a betöltését. Ha VPN-t használsz, próbáld meg, hogy kikapcsolod, majd újra betöltöd ezt az oldalt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ha ez nem működik, akkor is megnézheted ezt a videót a YouTube-on, de a Duck Player által nyújtott extra adatvédelem nélkül.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "A Duck Player személyre szabott hirdetések nélküli, letisztult megtekintési élményt nyújt, és megakadályozza, hogy a megtekintési tevékenységed befolyásolja a neked szóló YouTube-ajánlásokat." } diff --git a/special-pages/pages/duckplayer/public/locales/it/duckplayer.json b/special-pages/pages/duckplayer/public/locales/it/duckplayer.json index 9ce216677..464303fca 100644 --- a/special-pages/pages/duckplayer/public/locales/it/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/it/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRORE: ID video non valido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube non consente a Duck Player di caricare questo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "Questo video si può vedere solo su YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Puoi ancora guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube sta impedendo il caricamento di questo video. Se stai utilizzando una VPN, prova a disattivarla e a ricaricare questa pagina.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se il problema non si risolve, puoi comunque guardare questo video su YouTube, ma senza la privacy aggiuntiva offerta da Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/lt/duckplayer.json b/special-pages/pages/duckplayer/public/locales/lt/duckplayer.json index 1b282dd2c..c8dd25e38 100644 --- a/special-pages/pages/duckplayer/public/locales/lt/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/lt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KLAIDA: netinkamas vaizdo įrašo ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "„YouTube“ neleidžia „Duck Player“ įkelti šio vaizdo įrašo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "„YouTube“ neleidžia šio vaizdo įrašo žiūrėti ne „YouTube“ platformoje.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šį vaizdo įrašą vis dar gali žiūrėti „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "„YouTube“ blokuoja šio vaizdo įrašo įkėlimą. Jei naudoji VPN, pabandyk jį išjungti ir iš naujo įkelti šį puslapį.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jei tai neveikia, vis tiek gali žiūrėti šį vaizdo įrašą „YouTube“, bet be papildomo „Duck Player“ privatumo.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "„Duck Player“ užtikrina nepriekaištingą žiūrėjimo patirtį be suasmenintų reklamų ir neleidžia žiūrėjimo veiklai daryti įtakos „YouTube“ rekomendacijoms." } diff --git a/special-pages/pages/duckplayer/public/locales/lv/duckplayer.json b/special-pages/pages/duckplayer/public/locales/lv/duckplayer.json index 46d0bee8e..3ae376c31 100644 --- a/special-pages/pages/duckplayer/public/locales/lv/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/lv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "KĻŪDA: Nederīgs video ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube neļauj Duck Player ielādēt šo video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube neļauj skatīties šo video ārpus YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Šo videoklipu joprojām vari skatīties vietnē YouTube, taču bez papildu Duck Player konfidencialitātes.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bloķē šī video ielādi. Ja tu izmanto VPN, mēģini to izslēgt un pārlādēt šo lapu.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ja tas nedarbojas, joprojām vari skatīties šo video vietnē YouTube, taču bez papildu privātuma, ko nodrošina Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player nodrošina netraucētu skatīšanās pieredzi bez personalizētām reklāmām un neļauj skatīšanās darbībām ietekmēt tavus YouTube ieteikumus." } diff --git a/special-pages/pages/duckplayer/public/locales/nb/duckplayer.json b/special-pages/pages/duckplayer/public/locales/nb/duckplayer.json index 4c1d826f6..fef475447 100644 --- a/special-pages/pages/duckplayer/public/locales/nb/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/nb/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEIL: Ugyldig video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube lar ikke Duck Player laste denne videoen", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillater ikke visning av denne videoen utenfor YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fremdeles se videoen på YouTube, men uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkerer denne videoen fra å lastes. Hvis du bruker en VPN, kan du prøve å slå den av og laste denne siden på nytt.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Hvis ikke det virker, kan du fremdeles se videoen på YouTube, bare uten det ekstra personvernet som Duck Player tilbyr.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player tilbyr en ren seeropplevelse uten tilpassede annonser og forhindrer at seeraktiviteten din påvirker YouTube-anbefalingene dine." } diff --git a/special-pages/pages/duckplayer/public/locales/nl/duckplayer.json b/special-pages/pages/duckplayer/public/locales/nl/duckplayer.json index a1be8669e..51a0ece78 100644 --- a/special-pages/pages/duckplayer/public/locales/nl/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/nl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FOUT: ongeldige video-id", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube staat Duck Player niet toe om deze video te laden.", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube staat niet toe dat je deze video buiten YouTube bekijkt.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Je kunt deze video nog steeds bekijken op YouTube, maar zonder de extra privacy van Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokkeert het laden van deze video. Als je een VPN gebruikt, probeer deze dan uit te schakelen en deze pagina opnieuw te laden.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Als dit niet werkt, kun je deze video nog steeds op YouTube bekijken, maar dan zonder de extra privacy van Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." } diff --git a/special-pages/pages/duckplayer/public/locales/pl/duckplayer.json b/special-pages/pages/duckplayer/public/locales/pl/duckplayer.json index accd3fde5..8b633d415 100644 --- a/special-pages/pages/duckplayer/public/locales/pl/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/pl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "BŁĄD: nieprawidłowy identyfikator filmu", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nie zezwala na załadowanie tego filmu przez Duck Player", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nie zezwala na oglądanie tego filmu poza YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Nadal możesz oglądać ten film na YouTube, ale bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje ładowanie tego filmu. Jeśli korzystasz z sieci VPN, spróbuj ją wyłączyć i ponownie załadować tę stronę.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Jeśli to nie pomoże, nadal możesz oglądać ten film na YouTube, jednak bez dodatkowej prywatności, jaką zapewnia Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." } diff --git a/special-pages/pages/duckplayer/public/locales/pt/duckplayer.json b/special-pages/pages/duckplayer/public/locales/pt/duckplayer.json index a5bfca188..2ff6fad9c 100644 --- a/special-pages/pages/duckplayer/public/locales/pt/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/pt/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ERRO: ID de vídeo inválido", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "O YouTube não permite que o Duck Player carregue este vídeo", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "O YouTube não permite que vejas este vídeo fora do YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "O YouTube está a bloquear o carregamento deste vídeo. Se estiveres a usar uma VPN, tenta desativá-la e recarregar esta página.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Se isto não funcionar, continuas a poder ver este vídeo no YouTube, mas sem a privacidade adicional do Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/ro/duckplayer.json b/special-pages/pages/duckplayer/public/locales/ro/duckplayer.json index bfafec70e..25ffac535 100644 --- a/special-pages/pages/duckplayer/public/locales/ro/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/ro/duckplayer.json @@ -32,6 +32,26 @@ "title" : "EROARE: ID video incorect", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nu permite Duck Player să încarce acest videoclip", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nu permite ca acest videoclip să fie vizionat în afara YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blochează încărcarea acestui videoclip. Dacă folosești un VPN, încearcă să-l dezactivezi și să reîncarci această pagină.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Dacă acest lucru nu funcționează, poți viziona în continuare acest videoclip pe YouTube, dar fără confidențialitatea suplimentară oferită de Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player oferă o experiență de vizionare fără perturbări, fără reclame personalizate și împiedică activitatea de vizionare să îți influențeze recomandările YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/ru/duckplayer.json b/special-pages/pages/duckplayer/public/locales/ru/duckplayer.json index 4bf5cc0c1..d1a3fc528 100644 --- a/special-pages/pages/duckplayer/public/locales/ru/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/ru/duckplayer.json @@ -32,6 +32,26 @@ "title" : "ОШИБКА: Неверный идентификатор видео", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube не позволяет проигрывателю Duck Player загрузить это видео", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube не позволяет смотреть это видео вне своей платформы.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Вы по-прежнему можете посмотреть этот ролик на YouTube, но уже без дополнительной защиты Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube блокирует загрузку этого видео. Если вы используете VPN, отключите ее и перезагрузите страницу.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Если это не даст результата, вы все равно сможете просмотреть это видео на YouTube, но без дополнительной защиты конфиденциальности, обеспечиваемой Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/sk/duckplayer.json b/special-pages/pages/duckplayer/public/locales/sk/duckplayer.json index 88a2cc3a5..a68cdeb89 100644 --- a/special-pages/pages/duckplayer/public/locales/sk/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/sk/duckplayer.json @@ -32,6 +32,26 @@ "title" : "CHYBA: Neplatný identifikátor videa", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube nedovolí Duck Playeru načítať toto video", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube nepovoľuje, aby sa toto video pozeralo mimo YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Toto video si môžeš pozrieť aj na YouTube, ale bez dodatočného súkromia Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blokuje načítanie tohto videa. Ak používaš sieť VPN, skús ju vypnúť a znova načítať túto stránku.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Ak to nefunguje, môžeš si toto video pozrieť aj na YouTube, ale bez dodatočnej ochrany súkromia, ktorú poskytuje Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player poskytuje čisté zobrazenie bez personalizovaných reklám a zabraňuje tomu, aby aktivita pri sledovaní ovplyvňovala vaše odporúčania v službe YouTube." } diff --git a/special-pages/pages/duckplayer/public/locales/sl/duckplayer.json b/special-pages/pages/duckplayer/public/locales/sl/duckplayer.json index 7d4a89155..977830ef4 100644 --- a/special-pages/pages/duckplayer/public/locales/sl/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/sl/duckplayer.json @@ -32,6 +32,26 @@ "title" : "NAPAKA: Neveljaven ID videoposnetka", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube ne dovoli predvajalniku Duck Player naložiti tega videa", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube ne dovoljuje, da si ta videoposnetek ogledate zunaj YouTuba.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Ta videoposnetek si lahko še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube preprečuje nalaganje tega videoposnetka. Če uporabljate omrežje VPN, ga poskusite izklopiti in znova naložite to stran.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Če to ne deluje, si lahko ta videoposnetek še vedno ogledate na YouTubu, vendar brez dodane zasebnosti predvajalnika Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Predvajalnik Duck Player zagotavlja čisto izkušnjo gledanja brez prilagojenih oglasov in preprečuje, da bi dejavnost gledanja vplivala na vaša priporočila v YouTubu." } diff --git a/special-pages/pages/duckplayer/public/locales/sv/duckplayer.json b/special-pages/pages/duckplayer/public/locales/sv/duckplayer.json index cb5b75e4c..444e91750 100644 --- a/special-pages/pages/duckplayer/public/locales/sv/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/sv/duckplayer.json @@ -32,6 +32,26 @@ "title" : "FEL: Ogiltigt video-ID", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube låter inte Duck Player läsa in den här videon", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube tillåter inte att den här videon ses utanför YouTube.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Du kan fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube blockerar den här videon från att läsas in. Om du använder ett VPN kan du prova stänga av det och läsa in sidan igen.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Om det inte fungerar kan du fortfarande se den här videon på YouTube, men utan den extra integriteten från Duck Player.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player ger en störningsfri visningsupplevelse utan personliga annonser och förhindrar att din tittaraktivitet påverkar YouTube-rekommendationer." } diff --git a/special-pages/pages/duckplayer/public/locales/tr/duckplayer.json b/special-pages/pages/duckplayer/public/locales/tr/duckplayer.json index c23cab2bb..e733fcaa6 100644 --- a/special-pages/pages/duckplayer/public/locales/tr/duckplayer.json +++ b/special-pages/pages/duckplayer/public/locales/tr/duckplayer.json @@ -32,6 +32,26 @@ "title" : "HATA: Geçersiz video kimliği", "note" : "Shown when the page URL doesn't match a known video ID. Note for translators: The tag makes the word 'ERROR:' bold. Depending on the grammar of the target language, you might need to move it so that the correct word is emphasized." }, + "blockedVideoErrorHeading" : { + "title" : "YouTube, Duck Player'ın bu videoyu yüklemesine izin vermiyor", + "note" : "Message shown when YouTube has blocked playback of a video" + }, + "blockedVideoErrorMessage1" : { + "title" : "YouTube, bu videonun YouTube dışında izlenmesine izin vermiyor.", + "note" : "Explanation on why the error is happening." + }, + "blockedVideoErrorMessage2" : { + "title" : "Bu videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "A message explaining that the blocked video can be watched directly on YouTube." + }, + "signInRequiredErrorMessage1" : { + "title" : "YouTube bu videonun yüklenmesini engelliyor. VPN kullanıyorsanız, VPN'i kapatıp bu sayfayı yeniden yüklemeyi deneyin.", + "note" : "Explanation on why the error is happening and a suggestions on how to solve it." + }, + "signInRequiredErrorMessage2" : { + "title" : "Bu işe yaramazsa, videoyu Duck Player'ın sunduğu ek gizlilik olmadan YouTube'da izleyebilirsiniz.", + "note" : "More troubleshooting tips for this specific error" + }, "tooltipInfo" : { "title" : "Duck Player, kişiselleştirilmiş reklamlar olmadan temiz bir görüntüleme deneyimi sağlar ve görüntüleme etkinliğinin YouTube önerilerinizi etkilemesini önler." } diff --git a/special-pages/pages/duckplayer/src/index.js b/special-pages/pages/duckplayer/src/index.js index a9c40ae39..c677b427e 100644 --- a/special-pages/pages/duckplayer/src/index.js +++ b/special-pages/pages/duckplayer/src/index.js @@ -91,6 +91,14 @@ export class DuckplayerPage { return this.messaging.subscribe('onUserValuesChanged', cb); } + /** + * This will be sent if the application fails to load. + * @param {{error: import('../types/duckplayer.ts').YouTubeError}} params + */ + reportYouTubeError(params) { + this.messaging.notify('reportYouTubeError', params); + } + /** * This will be sent if the application has loaded, but a client-side error * has occurred that cannot be recovered from diff --git a/special-pages/pages/duckplayer/types/duckplayer.ts b/special-pages/pages/duckplayer/types/duckplayer.ts index bb0d047c8..765db8188 100644 --- a/special-pages/pages/duckplayer/types/duckplayer.ts +++ b/special-pages/pages/duckplayer/types/duckplayer.ts @@ -6,6 +6,7 @@ * @module Duckplayer Messages */ +export type YouTubeError = "age-restricted" | "sign-in-required" | "no-embed" | "unknown"; export type PrivatePlayerMode = | { enabled: unknown; @@ -26,6 +27,7 @@ export interface DuckplayerMessages { | OpenSettingsNotification | ReportInitExceptionNotification | ReportPageExceptionNotification + | ReportYouTubeErrorNotification | TelemetryEventNotification; requests: GetUserValuesRequest | InitialSetupRequest | SetUserValuesRequest; subscriptions: OnUserValuesChangedSubscription; @@ -62,6 +64,16 @@ export interface ReportPageExceptionNotification { export interface ReportPageExceptionNotify { message: string; } +/** + * Generated from @see "../messages/reportYouTubeError.notify.json" + */ +export interface ReportYouTubeErrorNotification { + method: "reportYouTubeError"; + params: ReportYouTubeErrorNotify; +} +export interface ReportYouTubeErrorNotify { + error: YouTubeError; +} /** * Generated from @see "../messages/telemetryEvent.notify.json" */ @@ -117,6 +129,16 @@ export interface DuckPlayerPageSettings { focusMode?: { state: "enabled" | "disabled"; }; + /** + * Configures a custom error message for YouTube errors + */ + customError?: { + state: "enabled" | "disabled"; + /** + * A selector that, when not empty, indicates a sign-in required error + */ + signInRequiredSelector: string; + }; } /** * Generated from @see "../messages/setUserValues.request.json"