Youtube Playables Js Sdk
Published by hungify in playables-skills
What this skill does
Use this skill to initialize and integrate the YouTube Playables JavaScript SDK into an HTML5 game. Covers SDK bootstrapping, game lifecycle signaling, persistent save data management, interstitial and rewarded ads, leaderboard score submission, player language detection, and audio/pause lifecycle hooks. Use when the user asks about YouTube Playables, building or embedding games for YouTube, the YT game SDK, YT Playables integration, or making an interactive playable game on YouTube.
Add Youtube Playables Js Sdk to your agent
Review the source and files first. When you are ready, copy the prompt instruction or use the CLI command supported by your environment.
Install with a prompt
Paste this into a compatible coding agent:
add this skill "youtube-playables-js-sdk" from https://github.com/hungify/playables-skillsInstall with the CLI
Run this command in a controlled environment after reviewing the repository:
npx skills add https://github.com/hungify/playables-skills --skill youtube-playables-js-sdkSkill instructions
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:
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.
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:
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.
// 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.
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.
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.
function submitScore(score) {
const value = typeof score === 'string' ? parseInt(score, 10) : score;
return ytgame.engagement.sendScore({ value });
}
Files included
- SKILL.md

