Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 71 additions & 2 deletions iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,22 @@
} = JSON.parse(UrlQueryData);

function sendMessageToRN(msg) {
window.ReactNativeWebView.postMessage(JSON.stringify(msg));

console.log('[youtube-iframe] sending message to RN', msg);

if (window.ReactNativeWebView) {
// React Native WebView
window.ReactNativeWebView.postMessage(JSON.stringify(msg));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: An interesting quirk is that adding targetOrigin here will break Android native.

} else if (window.parent) {
// This is from expo web iframe.
//
// We set cross origin to so that this will work on differnet origin.
// We don't expect any sensitive data to be sent.
window.parent.postMessage(JSON.stringify(msg), '*');
} else {
console.error('Not running in Iframe, cannot postMessage.');
return;
}
}

let metaString = '';
Expand Down Expand Up @@ -147,6 +162,8 @@
document.addEventListener('mozfullscreenchange', onFullScreenChange);
document.addEventListener('webkitfullscreenchange', onFullScreenChange);

// NOTE: the 3rd parameter is needed for Android Webview.
// https://github.com/react-native-webview/react-native-webview/issues/356
window.addEventListener('message', function (event) {
const {data} = event;

Expand Down Expand Up @@ -178,11 +195,63 @@
case 'setPlaybackRate':
player.setPlaybackRate(meta.playbackRate);
break;

case 'seekTo':
player.seekTo(meta.seconds, meta.allowSeekAhead);
break;

case 'loadVideoById':
if (meta.play) {
player.loadVideoById({videoId: meta.videoId});
} else {
player.cueVideoById({videoId: meta.videoId});
}
break;

case 'loadPlaylist':
const {playList, startIndex, play} = meta;
const index = startIndex || 0;
const func = play ? 'loadPlaylist' : 'cuePlaylist';

const list = typeof playList === 'string' ? playList : undefined;
const listType = typeof playList === 'string' ? 'playlist' : undefined;
const playlist = Array.isArray(playList) ? playList.join(',') : undefined;

player[func]({listType, list, playlist, index});
break;

case 'getVideoUrl':
sendMessageToRN({eventType: 'getVideoUrl', data: player.getVideoUrl()});
break;

case 'getDuration':
sendMessageToRN({eventType: 'getDuration', data: player.getDuration()});
break;

case 'getCurrentTime':
sendMessageToRN({eventType: 'getCurrentTime', data: player.getCurrentTime()});
break;

case 'isMuted':
sendMessageToRN({eventType: 'isMuted', data: player.isMuted()});
break;

case 'getVolume':
sendMessageToRN({eventType: 'getVolume', data: player.getVolume()});
break;

case 'getPlaybackRate':
sendMessageToRN({eventType: 'getPlaybackRate', data: player.getPlaybackRate()});
break;

case 'getAvailablePlaybackRates':
sendMessageToRN({eventType: 'getAvailablePlaybackRates', data: player.getAvailablePlaybackRates()});
break;
}
} catch (error) {
console.error('Error parsing data', event, error);
}
});
}, true);
</script>
</body>
</html>
120 changes: 60 additions & 60 deletions src/PlayerScripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,6 @@ export const PLAYER_FUNCTIONS = {
unMuteVideo: 'player.unMute(); true;',
playVideo: 'player.playVideo(); true;',
pauseVideo: 'player.pauseVideo(); true;',
getVideoUrlScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getVideoUrl', data: player.getVideoUrl()}));
true;
`,
durationScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getDuration', data: player.getDuration()}));
true;
`,
currentTimeScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getCurrentTime', data: player.getCurrentTime()}));
true;
`,
isMutedScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'isMuted', data: player.isMuted()}));
true;
`,
getVolumeScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getVolume', data: player.getVolume()}));
true;
`,
getPlaybackRateScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getPlaybackRate', data: player.getPlaybackRate()}));
true;
`,
getAvailablePlaybackRatesScript: `
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getAvailablePlaybackRates', data: player.getAvailablePlaybackRates()}));
true;
`,

setVolume: volume => {
return `player.setVolume(${volume}); true;`;
},

seekToScript: (seconds, allowSeekAhead) => {
return `player.seekTo(${seconds}, ${allowSeekAhead}); true;`;
},

setPlaybackRate: playbackRate => {
return `player.setPlaybackRate(${playbackRate}); true;`;
},

loadPlaylist: (playList, startIndex, play) => {
const index = startIndex || 0;
const func = play ? 'loadPlaylist' : 'cuePlaylist';

const list = typeof playList === 'string' ? `"${playList}"` : 'undefined';
const listType =
typeof playList === 'string' ? `"${playlist}"` : 'undefined';
const playlist = Array.isArray(playList)
? `"${playList.join(',')}"`
: 'undefined';

return `player.${func}({listType: ${listType}, list: ${list}, playlist: ${playlist}, index: ${index}}); true;`;
},

loadVideoById: (videoId, play) => {
const func = play ? 'loadVideoById' : 'cueVideoById';

return `player.${func}({videoId: ${JSON.stringify(videoId)}}); true;`;
},
};

export const playMode = {
Expand Down Expand Up @@ -276,6 +216,66 @@ export const MAIN_SCRIPT = (
case 'unMuteVideo':
player.unMute();
break;

case 'setVolume':
player.setVolume(parsedData.meta.volume);
break;

case 'setPlaybackRate':
player.setPlaybackRate(parsedData.meta.playbackRate);
break;

case 'seekTo':
player.seekTo(parsedData.meta.seconds, parsedData.meta.allowSeekAhead);
break;

case 'loadVideoById':
if (parsedData.meta.play) {
player.loadVideoById({videoId: parsedData.meta.videoId});
} else {
player.cueVideoById({videoId: parsedData.meta.videoId});
}
break;

case 'loadPlaylist':
const {playList, startIndex, play} = parsedData.meta;
const index = startIndex || 0;
const func = play ? 'loadPlaylist' : 'cuePlaylist';

const list = typeof playList === 'string' ? playList : undefined;
const listType = typeof playList === 'string' ? 'playlist' : undefined;
const playlist = Array.isArray(playList) ? playList.join(',') : undefined;

player[func]({listType, list, playlist, index});
break;

case 'getVideoUrl':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getVideoUrl', data: player.getVideoUrl()}));
break;

case 'getDuration':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getDuration', data: player.getDuration()}));
break;

case 'getCurrentTime':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getCurrentTime', data: player.getCurrentTime()}));
break;

case 'isMuted':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'isMuted', data: player.isMuted()}));
break;

case 'getVolume':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getVolume', data: player.getVolume()}));
break;

case 'getPlaybackRate':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getPlaybackRate', data: player.getPlaybackRate()}));
break;

case 'getAvailablePlaybackRates':
window.ReactNativeWebView.postMessage(JSON.stringify({eventType: 'getAvailablePlaybackRates', data: player.getAvailablePlaybackRates()}));
break;
}
} catch (error) {
console.error('Error parsing data', event, error);
Expand Down
55 changes: 30 additions & 25 deletions src/YoutubeIframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
DEFAULT_BASE_URL,
CUSTOM_USER_AGENT,
} from './constants';
import {MAIN_SCRIPT, PLAYER_FUNCTIONS} from './PlayerScripts';
import {MAIN_SCRIPT} from './PlayerScripts';
import {deepComparePlayList} from './utils';

const YoutubeIframe = (props, ref) => {
Expand Down Expand Up @@ -62,7 +62,10 @@ const YoutubeIframe = (props, ref) => {
}

const message = JSON.stringify({eventName, meta});
webViewRef.current.postMessage(message);

// NOTE: we set cross origin to so that this will work for webView for
// website hosted across different origin.
webViewRef.current.postMessage(message, '*');
},
[playerReady],
);
Expand All @@ -71,58 +74,52 @@ const YoutubeIframe = (props, ref) => {
ref,
() => ({
getVideoUrl: () => {
webViewRef.current.injectJavaScript(PLAYER_FUNCTIONS.getVideoUrlScript);
sendPostMessage('getVideoUrl', {});
return new Promise(resolve => {
eventEmitter.current.once('getVideoUrl', resolve);
});
},
getDuration: () => {
webViewRef.current.injectJavaScript(PLAYER_FUNCTIONS.durationScript);
sendPostMessage('getDuration', {});
return new Promise(resolve => {
eventEmitter.current.once('getDuration', resolve);
});
},
getCurrentTime: () => {
webViewRef.current.injectJavaScript(PLAYER_FUNCTIONS.currentTimeScript);
sendPostMessage('getCurrentTime', {});
return new Promise(resolve => {
eventEmitter.current.once('getCurrentTime', resolve);
});
},
isMuted: () => {
webViewRef.current.injectJavaScript(PLAYER_FUNCTIONS.isMutedScript);
sendPostMessage('isMuted', {});
return new Promise(resolve => {
eventEmitter.current.once('isMuted', resolve);
});
},
getVolume: () => {
webViewRef.current.injectJavaScript(PLAYER_FUNCTIONS.getVolumeScript);
sendPostMessage('getVolume', {});
return new Promise(resolve => {
eventEmitter.current.once('getVolume', resolve);
});
},
getPlaybackRate: () => {
webViewRef.current.injectJavaScript(
PLAYER_FUNCTIONS.getPlaybackRateScript,
);
sendPostMessage('getPlaybackRate', {});
return new Promise(resolve => {
eventEmitter.current.once('getPlaybackRate', resolve);
});
},
getAvailablePlaybackRates: () => {
webViewRef.current.injectJavaScript(
PLAYER_FUNCTIONS.getAvailablePlaybackRatesScript,
);
sendPostMessage('getAvailablePlaybackRates', {});
return new Promise(resolve => {
eventEmitter.current.once('getAvailablePlaybackRates', resolve);
});
},
seekTo: (seconds, allowSeekAhead) => {
webViewRef.current.injectJavaScript(
PLAYER_FUNCTIONS.seekToScript(seconds, allowSeekAhead),
);
sendPostMessage('seekTo', {seconds, allowSeekAhead});
},
}),
[],
[sendPostMessage],
);

useEffect(() => {
Expand Down Expand Up @@ -158,10 +155,8 @@ const YoutubeIframe = (props, ref) => {

lastVideoIdRef.current = videoId;

webViewRef.current.injectJavaScript(
PLAYER_FUNCTIONS.loadVideoById(videoId, play),
);
}, [videoId, play, playerReady]);
sendPostMessage('loadVideoById', {videoId, play});
}, [videoId, play, playerReady, sendPostMessage]);

useEffect(() => {
if (!playerReady) {
Expand All @@ -177,10 +172,12 @@ const YoutubeIframe = (props, ref) => {

lastPlayListRef.current = playList;

webViewRef.current.injectJavaScript(
PLAYER_FUNCTIONS.loadPlaylist(playList, playListStartIndex, play),
);
}, [playList, play, playListStartIndex, playerReady]);
sendPostMessage('loadPlaylist', {
playList,
startIndex: playListStartIndex,
play,
});
}, [playList, play, playListStartIndex, playerReady, sendPostMessage]);

const onWebMessage = useCallback(
event => {
Expand All @@ -193,6 +190,14 @@ const YoutubeIframe = (props, ref) => {
break;
case 'playerStateChange':
onChangeState(PLAYER_STATES[message.data]);
if (message.data === -1) {
// unstartred state is -1
// We will not normally "change" to this state, but it can happen
// if autoplay is not supported.
console.warn(
'[rn-youtube-iframe] Player unstarted - autoplay may be blocked.',
);
}
break;
case 'playerReady':
onReady();
Expand Down
Loading