티스토리 뷰
2020/12/23 - [프로그래밍/자바스크립트] - IFrame YOUTUBE API를 활용한 custom player 개발 1편
index.html
우선 필요한것은 개발에 필요한 레이아웃이다.
디버깅이 용이하고, 목표한 바를 이루려면
최대한 상상해야 한다.
의도한 결과와 무엇이 다르게 나올지 말이다.
어쩔 수 없다.
그냥 코딩하면 코딩의 결과물이 산으로 가버린다.
귀찮지만 준비과정은 확실해야한다.
나중에 머리가 꼬이고, 초기화 되는 머리가 소스를 다시 보았을 때 이해 하기 쉬우려면 어쩔 수 없다
더 빠른 생산, 그리고 좋은 유지보수를 위해 처음 시작은 명확하게 작성해야 하지만
만들다보면 나한테는 편하지만 남들이 보기에, 아니면 프로그래밍적으로 좋은 설계 방법일까는 의문이다.
여하튼 내부적인 구조야 어찌돼었건 우선 플러그인이라함은 잘 몰라도 간단하게 선언함으로써 사용할 수 있어야 한다.
우선 index.html
<html>
<head>
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
<div class="ytv-player" data-video-id="NYqpNbHkRL4"></div>
<iframe width="560" height="315" src="https://www.youtube.com/embed/mifaFpdROvY" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe id="gangnamStyleIframe"
width="560"
height="315"
src="https://www.youtube.com/embed/9bZkp7q19f0?rel=0&enablejsapi=1&autoplay=1"
frameborder="0"
allow="autoplay; fullscreen"
allowfullscreen></iframe>
<script src="https://www.youtube.com/iframe_api" async defer></script>
<script src="./index.js" async defer></script>
</body>
</html>
설명을 하자면
처음 div는 데모 예제와 다를것이 없다
예제에서 해당 방법을 사용했기에 개발이 용이하고 따라하기 쉽기 때문이다.
두번째는 일반적인 유튜브에서 퍼가기 기능을 사용했을때 나오는 소스이다.
이 상황에서 사용가능해야 사용자들이 사용하기 쉬울 것이다.
세번째는 iframeAPI를 iframe에서 직접 불러오는 방법인데 뒤에 부수적인 파라미터가 붙는다
세번째에서 두번째로 가기 위한 과정에 있는 소스이다.
두번째가 되면 세번째는 필요 없다.
단지 개발 중에 필요하기 때문에 넣었다.
그리고 간단한 UI가 보여져야 하기에
css도 구성해야하는데 귀찮기때문에 tailwind를 사용하였다.
본격적인 제작
이제 index.js를 만들어 소스를 구성하자
/* 1번 */
var onYouTubeIframeAPIReady = async () => { }
/* 2번 */
async function onYouTubeIframeAPIReady () { }
앞선글에서 iframe api 스크립트가 로드 되면
onYouTubeIframeAPIReady 을 호출한다고 했었다.
그렇기에 이번 소스는 onYouTubeIframeAPIReady 안에 모두 선언되고 관리될 것이다.
1번의 방법 2번의 방법 둘 다 상관없으나 작성자는 1번을 선호한다.
왜냐면 1번이 타자를 좀 더 덜쳐도 되기 때문이다.
이유를 덧붙이자면 최근 문법에서 권장되는 화살표 문법이기도 하고…
const나 let으로 선언할수도 있지만 해당 선언을 하면 iframeAPI에서 함수를 찾지 못한다
아마 const나 let의 경우 선언위치에 따라 함수가 사용이 불가할때도 있어서 그런듯 하다.
이제 index.html에 있는 div, iframe등을 컨트롤이 가능한 객체로 바꾸어야 한다
컨트롤이 가능한 객체로
우선 예제코드와 유사한 div부터 바꾸어보자.
const player = []; // 컨트롤 가능한 플레이어들이 들어가는 변수 선언.
const players = document.querySelectorAll(".ytv-player"); //플레이어 호출.
players.forEach((_player, i) => { //플레이어가 있다면,
player[i] = new YT.Player(_player, {
height: "360",
width: "640",
videoId: _player.dataset.videoId,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
});
console.log(player);
function onPlayerReady(event) {
console.log('onPlayerReady 실행');
}
function onPlayerStateChange(event) {
console.log('플레이어의 상태 변화를 감지');
}
ytv-player라는 클래스의 div를 모조리 가져와
foreach문으로 하나하나씩 등록하는 구문이다.
index.html을 실행시켜보면 예제 코드 ID인 유튜브 영상.
(이미지다 재생 버튼을 누르면 낚인것이다)
처럼 유튜브 영상이 뜬다면 성공한것이다.
이렇게 성공헀지만 문제점이 하나 있다.
단독으로 존재하는 iframe이기에
해당 창 옆에 컨트롤창을 추가한다거나, 또는 팝업창을 영상 위에 띄운다거나 하는 컨트롤이 어렵다.
즉 해당 아이프레임과 동일한 크기를 가진 부모창 요소가 필요하다.
jquery에선 wrap으로 간단하게 해결할 수 있지만 왠만하면 jquery는 남이 짠 코드에서만 쓰고 내가 만든 코드에선 쓰지 말자는 주의라서 조금 불편한 작성을 사용하겠다.
players.forEach((_player, i) => { //플레이어가 있다면,
__player = document.createElement("div");
wrapper = document.createElement("div");
wrapper.className = "ytv-wrapper relative inline-block";
wrapper.dataset.videoId = _player.dataset.videoId;
_player.appendChild(wrapper);
wrapper.appendChild(__player);
_player.className = "w-full ytv-parent inline-block";
player[i] = new YT.Player(__player, {
height: "360",
width: "640",
wmode: 'transparent',
videoId: _player.dataset.videoId,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
});
복잡하지만 소스는 간단하다.
div안에 div를 추가하고 div를 하나 더 추가헀다(1이 2에게 3하는 것이 떠오른다)
하나는 줄바꿈과 추가적인 컨트롤 요소가 들어가기 위한 최상위 부모 div
하나는 iframe의 부모 역할을 해줄 div
tailwind를 사용하므로 style대신 class에 해당 스타일을 부여했으며
워낙 직관적인 클래스명이라 어떤 스타일인지는 굳이 설명하지 않아도 될 것이다.
다음은 iframe처리.
iframe처리는 간단하게 해당 엘리먼트에 api를 적용할거라면 상관없지만
지금 만들고자 하는 플러그인에서 필요한 컨트롤바를 위한 div가 있을땐 문제가 된다.
해당 요소를 innerHTML로 심플하게 만들어버리면 등록된 이벤트가 초기화 되버리고 마찬가지로 appendChild insertBefore등을 이용해서 한다면 부모 div가 없이 바로 body에서 iframe이 제 위치를 찾아주기 어렵다.
굳이 body에 바로 선언된것이 아니더라도 형제노드가 많으면 더더욱 그렇다.
new YT.Player를 이용한 컨트롤 가능한 유튜브 플레이어를 만드는걸 불가능하게 되버린다.
이 해결 과정은 삽질의 연속이고 설명하기 힘들어 일일이 나열하진 않겠지만
그래서 3번의 소스를 작성하고 2번은 어떻게 구현해야할까 짱구를 신나게 굴리던 도중.
그냥 iframe을 위 div처럼 변환후 div로 처리하자는게 결론이다
서로 다른형태지만 같은 결과물이 나와야 하는 상황이라면,
다른 형테 하나하나 다 맞는 소스 처리보단 하나의 표준을 정하고 그 표준으로 변환 후 소스를 적용시키는게 낫다는 결론이다.
자 이제 iframe을 div로 변경하자.
const iframes = document.querySelectorAll('iframe[src^="https://www.youtube.com/embed/"]');
iframes.forEach((_player, i) => { //아이프레임 플레이어라면 플레이어로 바꿈.
_player.className = "ytv-player";
_player.dataset.videoId = youtubeId(_player.src);
_player.outerHTML = _player.outerHTML.replace(/iframe/g,"div");
});
소스는 간단하다. iframe중 유튜브 소스를 가져와서
foreach문으로 반복문을 돌리며 div로 만들어준다.
핵심코드는
_player.outerHTML = _player.outerHTML.replace(/iframe/g,"div");
이다. iframe을 div로 변경해준다.
그리고 앞서 변경한 소스와 동일하게 하기 위해
clasName에 해당 클래스를 넣고
player data 속성에 src에서 추출한 유튜브 아이디를 넣는다.
const youtubeId = (url) => {
var tag = "";
if(url) {
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var matchs = url.match(regExp);
if (matchs) {
tag += matchs[7];
}
return tag;
}
}
필요한 유튜브 아이디 정규식 추출 함수는 위의 것을 사용했다.
자 실행해보자
짜잔!?
결과가 이상하게 나온다.
iframe만 있을땐 정상동작하고
마찬가지로 div만 있을때도 정상동작하는데
두개가 같이 있는 경우 제대로 되지 않는다.
이유는 바로 자바스크립트의 언어적 특성때문이다.
자바스크립트의 경우 순차적으로 실행하는것이 아닌
동시에 실행하고 먼저 마무리 되는 순서대로 처리가 되어버린다.
의도한것은 iframe을 div로 변환하고,
div의 for문을 돌린거지만
자바스크립트의 특성상 div의 for문과 iframe의 for문이 독립적으로 돌아가버리니 iframe을 div로 바꾼 div를 인식하지 못하는 현상이 발생할때도 있고 아닐때도 있는 것이다.
이를 위해서 비동기 처리를 해줘야하는데
사용가능한것들이 바로 콜백, promise, async이다
콜백과 프로미스는 복잡하기 그지 없고 직관적이지 못하다.
과거에는 해당 방법밖에 없기에 콜백지옥을 감수하며 썼지만
async를 활용하자.
await는 await가 선언된경우
해당 위의 함수가 실행 된 후에 아래의 함수를 순차적으로 실행하게 할 수 있다.
단 await는 async가 정의된 함수에서만 사용가능하다.
그러니
var onYouTubeIframeAPIReady = async () {}
형태로 바꾸어주자.
그리고 순서상 iframe이 먼저 실행되어야 하니 해당 iframe구문을 div변환 순서보다 위로 올리고
아래와 같이 수정하자
await iframes.forEach(async (_player, i) => { //아이프레임 플레이어라면 플레이어로 바꿈.
_player.className = "ytv-player";
_player.dataset.videoId = youtubeId(_player.src);
_player.outerHTML = _player.outerHTML.replace(/iframe/g,"div");
});
const players = document.querySelectorAll(".ytv-player"); //플레이어 호출.
await players.forEach(async (_player, i) => { //플레이어가 있다면,
__player = document.createElement("div");
wrapper = document.createElement("div");
wrapper.className = "ytv-wrapper relative inline-block";
wrapper.dataset.videoId = _player.dataset.videoId;
_player.appendChild(wrapper);
wrapper.appendChild(__player);
_player.className = "w-full ytv-parent inline-block";
player[i] = new YT.Player(__player, {
height: "360",
width: "640",
wmode: 'transparent',
videoId: _player.dataset.videoId,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
이제 iframe이든 div든 어떤 형식의 유튜브 플레이어든 간에 컨트롤 가능한 형태로 바뀌었을 것이며
이 컨트롤 가능한 상태의 DOM은 player변수에 순차적으로 담겨있을 것이다.
이제 이것들을 활용하여 목표하고자 하는 기능들을 개발해보도록 하려 한다
다음편에서 이어진다
'프로그래밍 > 자바스크립트' 카테고리의 다른 글
웹어셈블리와 FFMpeg (0) | 2020.12.26 |
---|---|
Webassembly + Rust의 다양한 빌드 방법. (0) | 2020.12.24 |
IFrame YOUTUBE API를 활용한 custom player 개발 1편 (0) | 2020.12.23 |
클라이언트(사용자)쪽에서 비디오 썸네일 뽑기 (0) | 2020.12.22 |
innerHTML을 사용할때 주의 할 점. (0) | 2020.12.22 |
- Total
- Today
- Yesterday
- Rust
- API
- craco
- HWP
- contributor
- iframe
- #useState 특징
- KBL
- contributer
- ffmpeg
- #useState
- 티스토리API
- vtie
- 무관의 왕
- webassembly
- 웹어셈블리
- 노력만 가상한 글
- #그래프
- 마크다운
- 7위를 하는 이유
- JavaScript
- #노력만 가상한 글
- 배포
- 팀별 분석
- youtube
- WASM
- 그래프
- #업데이트가 이상하게 될떄
- vscode
- #팀별 분석
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |