youtube-playables-js-sdk

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

YouTube Playables JavaScript SDK

YouTube Playables JavaScript SDK

Installation

安装

The
ytgame
global is injected by the YouTube host — there is nothing to install. Poll until it appears, then resolve:
js
function waitForYtgame(timeoutMs = 10000) {
    return new Promise((resolve, reject) => {
        if (window.ytgame) {
            resolve(window.ytgame);
            return;
        }

        const deadline = Date.now() + timeoutMs;
        const intervalId = setInterval(() => {
            if (window.ytgame) {
                clearInterval(intervalId);
                resolve(window.ytgame);
            } else if (Date.now() >= deadline) {
                clearInterval(intervalId);
                reject(new Error('ytgame SDK not available — running outside YouTube Playables?'));
            }
        }, 100);
    });
}
ytgame
全局对象由 YouTube 宿主注入,无需安装任何内容。持续轮询直到该对象出现,然后解析(resolve):
js
function waitForYtgame(timeoutMs = 10000) {
    return new Promise((resolve, reject) => {
        if (window.ytgame) {
            resolve(window.ytgame);
            return;
        }

        const deadline = Date.now() + timeoutMs;
        const intervalId = setInterval(() => {
            if (window.ytgame) {
                clearInterval(intervalId);
                resolve(window.ytgame);
            } else if (Date.now() >= deadline) {
                clearInterval(intervalId);
                reject(new Error('ytgame SDK not available — running outside YouTube Playables?'));
            }
        }, 100);
    });
}

Initialization

初始化

Sequence: wait for
ytgame
→ read language + load saved data in parallel → call
firstFrameReady()
→ call
gameReady()
once fully interactive.
js
let ytgame = null;
let platformLanguage = 'en';
let savedData = {};

waitForYtgame()
    .then((sdk) => {
        ytgame = sdk;

        const getLanguagePromise = ytgame.system.getLanguage().then((language) => {
            platformLanguage = language.length > 2 ? language.slice(0, 2) : language;
        });

        const getDataPromise = ytgame.game.loadData().then((data) => {
            if (typeof data === 'string' && data !== '') {
                try {
                    savedData = JSON.parse(data);
                } catch (e) {
                    console.error('Failed to parse saved data, starting fresh', e);
                    savedData = {};
                }
            }
        });

        ytgame.system.onAudioEnabledChange((isEnabled) => {
            // Mute or unmute the game audio based on isEnabled
        });

        ytgame.system.onPause(() => {
            // Pause the game loop and audio
        });

        ytgame.system.onResume(() => {
            // Resume the game loop and audio
        });

        return Promise.all([getLanguagePromise, getDataPromise]);
    })
    .then(() => {
        ytgame.game.firstFrameReady();
    })
    .catch((err) => {
        console.error('YouTube Playables SDK initialization failed', err);
        // Fall back to a non-Playables code path if needed
    });
When the game is fully interactive:
js
function notifyGameReady() {
    ytgame.game.gameReady();
}
执行顺序:等待
ytgame
→ 并行读取语言和加载存档数据 → 调用
firstFrameReady()
→ 游戏完全可交互后调用一次
gameReady()
。
js
let ytgame = null;
let platformLanguage = 'en';
let savedData = {};

waitForYtgame()
    .then((sdk) => {
        ytgame = sdk;

        const getLanguagePromise = ytgame.system.getLanguage().then((language) => {
            platformLanguage = language.length > 2 ? language.slice(0, 2) : language;
        });

        const getDataPromise = ytgame.game.loadData().then((data) => {
            if (typeof data === 'string' && data !== '') {
                try {
                    savedData = JSON.parse(data);
                } catch (e) {
                    console.error('Failed to parse saved data, starting fresh', e);
                    savedData = {};
                }
            }
        });

        ytgame.system.onAudioEnabledChange((isEnabled) => {
            // Mute or unmute the game audio based on isEnabled
        });

        ytgame.system.onPause(() => {
            // Pause the game loop and audio
        });

        ytgame.system.onResume(() => {
            // Resume the game loop and audio
        });

        return Promise.all([getLanguagePromise, getDataPromise]);
    })
    .then(() => {
        ytgame.game.firstFrameReady();
    })
    .catch((err) => {
        console.error('YouTube Playables SDK initialization failed', err);
        // Fall back to a non-Playables code path if needed
    });
游戏完全可交互时:
js
function notifyGameReady() {
    ytgame.game.gameReady();
}

Storage (saved data)

存储(存档数据)

Saved data is a single JSON-serializable blob per player. Always read → merge → write to avoid clobbering unrelated keys. After writing, re-read to confirm persistence.
js
// Read one or many keys from saved data
function getDataFromStorage(key, tryParseJson) {
    return ytgame.game.loadData().then((data) => {
        if (typeof data === 'string' && data !== '') {
            try {
                savedData = JSON.parse(data);
            } catch (e) {
                console.error('loadData parse error', e);
            }
        }

        const readOne = (k) => {
            let value = typeof savedData[k] === 'undefined' ? null : savedData[k];
            if (typeof value === 'string' && tryParseJson) {
                try {
                    value = JSON.parse(value);
                } catch (e) {
                    /* keep as-is */
                }
            }
            return value;
        };

        return Array.isArray(key) ? key.map(readOne) : readOne(key);
    });
}

// Write one or many keys, then verify by reloading
function setDataToStorage(key, value) {
    const data = savedData !== null ? { ...savedData } : {};

    if (Array.isArray(key)) {
        for (let i = 0; i < key.length; i++) data[key[i]] = value[i];
    } else {
        data[key] = value;
    }

    return ytgame.game
        .saveData(JSON.stringify(data))
        .then(() => {
            savedData = data;
            return ytgame.game.loadData();
        })
        .then((persisted) => {
            if (persisted !== JSON.stringify(data)) {
                console.warn(
                    'saveData verification mismatch — persisted value differs from written value'
                );
            }
        })
        .catch((err) => {
            console.error('saveData failed', err);
        });
}

// Delete one or many keys
function deleteDataFromStorage(key) {
    const data = savedData !== null ? { ...savedData } : {};

    if (Array.isArray(key)) {
        for (const k of key) delete data[k];
    } else {
        delete data[key];
    }

    return ytgame.game
        .saveData(JSON.stringify(data))
        .then(() => {
            savedData = data;
        })
        .catch((err) => {
            console.error('deleteData (saveData) failed', err);
        });
}
每位玩家的存档数据是一个可进行 JSON 序列化的数据块。始终按“读取 → 合并 → 写入”的顺序操作,以避免覆盖无关键。写入后,重新读取数据以确认已成功持久化。
js
// Read one or many keys from saved data
function getDataFromStorage(key, tryParseJson) {
    return ytgame.game.loadData().then((data) => {
        if (typeof data === 'string' && data !== '') {
            try {
                savedData = JSON.parse(data);
            } catch (e) {
                console.error('loadData parse error', e);
            }
        }

        const readOne = (k) => {
            let value = typeof savedData[k] === 'undefined' ? null : savedData[k];
            if (typeof value === 'string' && tryParseJson) {
                try {
                    value = JSON.parse(value);
                } catch (e) {
                    /* keep as-is */
                }
            }
            return value;
        };

        return Array.isArray(key) ? key.map(readOne) : readOne(key);
    });
}

// Write one or many keys, then verify by reloading
function setDataToStorage(key, value) {
    const data = savedData !== null ? { ...savedData } : {};

    if (Array.isArray(key)) {
        for (let i = 0; i < key.length; i++) data[key[i]] = value[i];
    } else {
        data[key] = value;
    }

    return ytgame.game
        .saveData(JSON.stringify(data))
        .then(() => {
            savedData = data;
            return ytgame.game.loadData();
        })
        .then((persisted) => {
            if (persisted !== JSON.stringify(data)) {
                console.warn(
                    'saveData verification mismatch — persisted value differs from written value'
                );
            }
        })
        .catch((err) => {
            console.error('saveData failed', err);
        });
}

// Delete one or many keys
function deleteDataFromStorage(key) {
    const data = savedData !== null ? { ...savedData } : {};

    if (Array.isArray(key)) {
        for (const k of key) delete data[k];
    } else {
        delete data[key];
    }

    return ytgame.game
        .saveData(JSON.stringify(data))
        .then(() => {
            savedData = data;
        })
        .catch((err) => {
            console.error('deleteData (saveData) failed', err);
        });
}

Advertisement

广告

Interstitial

插屏广告

ytgame.ads.requestInterstitialAd()
resolves when the ad flow ends. Pause before requesting; resume after resolution or rejection.
js
function showInterstitial() {
    // Pause game and mute audio before requesting
    ytgame.ads
        .requestInterstitialAd()
        .then(() => {
            // Resume game and unmute audio
        })
        .catch(() => {
            // Ad failed to deliver — resume game and continue
        });
}
ytgame.ads.requestInterstitialAd()
会在广告流程结束时完成(resolve)。请求前应暂停游戏;resolve 或 reject 后恢复游戏。
js
function showInterstitial() {
    // Pause game and mute audio before requesting
    ytgame.ads
        .requestInterstitialAd()
        .then(() => {
            // Resume game and unmute audio
        })
        .catch(() => {
            // Ad failed to deliver — resume game and continue
        });
}

Rewarded

激励广告

ytgame.ads.requestRewardedAd(placement)
resolves with a boolean. Grant the reward only when the resolved value is truthy.
js
function showRewarded(placement) {
    // Pause game and mute audio before requesting
    ytgame.ads
        .requestRewardedAd(placement)
        .then((isRewardEarned) => {
            if (isRewardEarned) {
                // Grant reward to the player
            }
            // Resume game and unmute audio
        })
        .catch(() => {
            // Ad failed to deliver — resume game and continue
        });
}
ytgame.ads.requestRewardedAd(placement)
会以 boolean 值完成(resolve)。仅当结果为真值时才能向玩家发放奖励。
js
function showRewarded(placement) {
    // Pause game and mute audio before requesting
    ytgame.ads
        .requestRewardedAd(placement)
        .then((isRewardEarned) => {
            if (isRewardEarned) {
                // Grant reward to the player
            }
            // Resume game and unmute audio
        })
        .catch(() => {
            // Ad failed to deliver — resume game and continue
        });
}

Leaderboards

排行榜

Submit integer scores via
ytgame.engagement.sendScore({ value })
. Coerce strings to integers before sending.
js
function submitScore(score) {
    const value = typeof score === 'string' ? parseInt(score, 10) : score;
    return ytgame.engagement.sendScore({ value });
}
通过
ytgame.engagement.sendScore({ value })
提交整数分数。发送前需将字符串强制转换为整数。
js
function submitScore(score) {
    const value = typeof score === 'string' ? parseInt(score, 10) : score;
    return ytgame.engagement.sendScore({ value });
}