안녕하세요.
제가 만들고 있는 앱은 react-native-webview로 유튜브 영상을 불러와 webview와 분리된 외부 react 컴포넌트에서 유튜브 영상을 일시정지 및 재생과 같은 컨트롤을 할 수 있는 앱을 만들고 있습니다. (iOS는 react-native-youtube를 사용하고 있습니다)
방식은 webview에 사용될 html text 파일을 하나 만든 후, message 주고 받을 수 있는 메소드를 추가하여 외부 컴포넌트와 소통할 수 있도록 만들어 놨습니다.
postMessage = (type, msg) => {
const message = createMessage(type, msg);
this.YTPlayer.postMessage(message);
};
onListenMessage = (event) => {
const msg = event.nativeEvent.data;
const parsingData = JSON.parse(msg);
const { type, message } = parsingData;
switch (type) {
case 'CURRENTTIME':
this.props.onProgress(message);
break;
case 'PLAYING':
this.setState({ state: 'PLAYING' }, this.props.onChangeContentsPlay());
break;
case 'PAUSED':
this.setState({ state: 'PAUSED' }, this.props.onChangeContentsPause());
break;
case 'UNSTARTED':
this.setState({ state: 'UNSTARTED' }, this.props.onChangeContentsPause());
break;
case 'ERROR':
this.setState({ state: 'ERROR' }, this.props.onError(message));
break;
case 'ENDED':
this.setState({ state: 'ENDED' }, this.seekTo(this.props.startTime));
break;
default:
break;
}
}
<WebView
ref={this.onRef}
originWhitelist={['*']}
javaScriptEnabled
domStorageEnabled
mixedContentMode="always"
onMessage={this.onListenMessage}
source={YTsource(YTid, YOUTUBE_HEIGHT)}
/>
const YTsource = (videoId, height) => ({
html: `<html>
<style type="text/css">
html, body {
margin: 0;
padding: 0;
position: relative;
}
</style>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width">
</head>
<body>
<div id="YTPlayer"></div>
</body>
<script type="text/javascript">
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
var timerId = 0;
function onYouTubeIframeAPIReady() {
player = new YT.Player('YTPlayer', {
videoId: '${videoId}',
height: ${height},
width: '100%',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
'onError': onPlayerError,
},
playerVars: {
rel: 0,
disablekb: 1,
// origin: 'https://www.youtube.com' not work!!!
}
});
}
function onPlayerReady(event) {
player.unloadModule("captions");
player.setOption("captions", "track", {"languageCode": "es"});
event.target.setVolume(100);
}
function onPlayerStateChange(event) {
clearTimeout(timerId);
switch (event.data) {
case YT.PlayerState.UNSTARTED:
onStateChange("UNSTARTED");
return;
break;
case YT.PlayerState.ENDED:
onStateChange("ENDED");
break;
case YT.PlayerState.PLAYING:
onStateChange("PLAYING");
timerId = setInterval(function () {
setCurrentSeconds();
}, 100);
return;
break;
case YT.PlayerState.PAUSED:
onStateChange("PAUSED");
return;
break;
case YT.PlayerState.BUFFERING:
onStateChange("BUFFERING");
return;
break;
case YT.PlayerState.CUED:
onStateChange("CUED");
return;
break;
}
}
function onPlayerError(err) {
var error = JSON.stringify(err);
var msg = createWebViewMessage('ERROR', error);
window.ReactNativeWebView.postMessage(msg);
}
window.document.addEventListener('message', function(e) {
setFunction(e.data);
});
window.chopchopPlayerBridge = {
sendStateChange: function (state) {
var msg = createWebViewMessage(state, state.toLowerCase())
window.ReactNativeWebView.postMessage(msg);
}
}
function createWebViewMessage(type, msg) {
var message = { type: type, message: msg };
return JSON.stringify(message);
}
function setFunction(msg) {
const { type, message } = JSON.parse(msg);
switch (type) {
case 'PLAY':
playVideo();
break;
case 'PAUSE':
pauseVideo();
break;
case 'SEEKTO':
seekTo(message);
break;
default:
break;
}
}
function pauseVideo() {
player.pauseVideo();
}
function playVideo() {
player.playVideo();
}
function seekTo(startSeconds) {
player.seekTo(startSeconds, true);
}
</script>
</html>`
});
코드는 대략 이렇게 구현되어 있습니다.
대부분의 영상들은 문제없이 실행되고, 컨트롤 할 수 있었으나,
VEVO와 같이 저작권이 강하게 걸려있는 음악영상들은 재생이 안되는 큰 문제가 발생했습니다.
그래서 몇가지 방법을 찾아보니,
-
영상 요청시 Referer header 값을 추가하면 된다고 하더군요 참조
react-native-webview 문서에도 request를 보낼때 header값을 추가할 수 있는 방법이 나와 있지만,
제가 현재 사용하는 방식인 html 소스를 그대로 갖다 쓰는 방식에서는 사용할 수가 없었습니다. -
youtube iframe api를 사용할 때, parameter값으로 origin 값을 추가하는 방식 참조
을 사용하고자 했으나, 안드로이드 뿐만아니라 웹에서도 적용되지 않았습니다.
참고로, iOS에서 사용하는 react-native-youtube는 origin 파라미터를 제공하여 아래와 같은 방식으로 VEVO영상을 재생할 수 있게 해주고 있습니다.
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if ([request.URL.host isEqual: self.originURL.host]) {
return YES;
} else if ([request.URL.scheme isEqual:@"ytplayer"]) {
[self notifyDelegateOfYouTubeCallbackUrl:request.URL];
return NO;
} else if ([request.URL.scheme isEqual: @"http"] || [request.URL.scheme isEqual:@"https"]) {
return [self handleHttpNavigationToUrl:request.URL];
}
return YES;
}
위에서 찾은 방법들을 통한 간단한 테스트 그리고 react-native-youtube를 참고해보니 VEVO와 같은 영상들은 최초요청할때 정상적인 domain값이 필요하다고 판단을 했습니다.
그런데, html 텍스트를 그대로 가져다 불러오는 저의 상황에서 어떤 부분을 손을 봐야할지 막막합니다.
혹시 해결할 수 있는 힌트를 알고 계신분이 있으시다면 공유해주셨으면 좋겠습니다.
답변 부탁드립니다.
감사합니다.