Help us understand the problem. What is going on with this article?

WebGLでエモいパーティクルを飛ばして音楽を再生する!

本記事は、WebGL Advent Calendar 2018 の 19 日目の記事となります。

はじめに

普段から私はYoutubeを見まくっています。

主に洋楽の激しい感じのジャンルの音楽(Post-hardcore、Pop punk、Djentとか)を聞くことが多いのですが、いわゆる激しい感じのジャンルのバンド界隈が良く使っているMVがあるのです。

共感して頂けた方は、もれなく私と音楽の趣味が合うでしょう。

まずはそのMVがどんなものか見てもらいましょう。
以下に参考例を上げました。

Bring Me The Horizon - Avalanche
https://youtu.be/EM6LGEfFmn0

Issues - Rank Rider
https://youtu.be/NCUclCLdxZ0

Whisperer - Confide
https://youtu.be/-qcgbtE2SvY

このような、アニメーションを織り交ぜながらcdジャケットを全面に出しているMVです。

今回、このようなMVを"ビジュアルMV"と呼ぶことにします。
(正しい呼び方がわかる方は教えていただきたい。。)

まずは完成物

まずは出来たものを見てもらったほうが早いかと思います。
以下よりご覧頂ければと思います。

main.png

particle music player🌟

ちなみに「ラウドロック」は和製英語らしいですね。。知らなかった。。

ラウドロック(Loud rock)は、ヘヴィメタルやハードコアなどから派生したロックのジャンルの一種。類似するジャンルのミクスチャー・ロックやモダン・ヘヴィネスなどと同様に和製英語である。

出典: フリー百科事典『ウィキペディア(Wikipedia)』

この「particle music player🌟」はブラウザに音声ファイルをドロップすることで再生可能となっております。

この先の記事をノリノリで見て頂く為にも、是非ともお好きな曲を聴きながら記事を読み進めて頂ければと思います。

※音声ファイルをドロップ後、再生ボタンを押すと1曲ループで再生されます。
※音量には十分にご注意ください。

"ビジュアルMV"っぽさとは何か

作成するにあたり、例のようなMVの
どのような要素が「あ~それっぽい」という共感を生み出すのか、冷静に分析。
(分析するほどのことではない。)

結果、以下4点が重要なのではないかと考えました。

1.CDのジャケットと曲名をかっこよく登場させる
2.オブジェクトをユラユラさせる
3.パーティクルを飛び散らかす
4.オーディオビジュアライザを表示させる

それぞれの実装について、部分的にコードを紹介したいと思います。

1.CDのジャケットと曲名をかっこよく登場させる

ファーストインパクトは重要です。

今回は仮のCDジャケットと仮の曲名を左右からフェードインさせ、フェードイン後、中央に縦線を出現させるシンプルなものです。

これはCSSアニメーションで実装しています。

style.css
#lp{
    animation: left-in 3s ease-in-out;
}

#lp::after{
    box-sizing: inherit;
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    transform-origin: center;
    border-right: 4px solid white;
    visibility: hidden;
    animation: line-fade 2s ease 3s ;
}

#cap{
    margin: 0 auto;
    animation: right-in 3s ease-in-out;
}

@keyframes left-in {
    0%{ left: -700px; }
    100%{ left: 0; }
}

@keyframes right-in {
    0%{ right: -700px; }
    100%{ right: 0; }
}

@keyframes line-fade {
    0%{ transform: scale3d(1,0,1); visibility: visible; }
    100%{ transform: scale3d(1,1,1); visibility: visible;}
}

摘要
・アニメーション時間はCDジャケットと曲名部分を3秒で表示、中央縦線はanimation-fill-modeのforwardsを指定し、CDジャケットが表示されたらそこから2秒かけて縦線を表示。
・中央の縦線はCDジャケット側の要素に疑似要素(::after)を使って表示。

2.オブジェクトをユラユラさせる

"ビジュアルMV"は、基本的にはCDジャケット、曲名などのオブジェクトがユラユラしています。

この浮遊感がより一層"ビジュアルMV"っぽさを出しています。

ユラユラについてもCSSアニメーションで表現しています。

style.css
#lp{
    animation: left-in 3s ease-in-out, horizontal 1.5s ease-in-out infinite alternate;
}

#cap{
    margin: 0 auto;
    animation: right-in 3s ease-in-out, horizontal 1.5s ease-in-out infinite alternate;
}

.lpimg{
    position: absolute;
    top: 50%;
    left: 50%;
    width: 50%;
    margin: 0 auto;
    animation: vertical 1s ease-in-out infinite alternate;
}

@keyframes horizontal {
    0% {transform:translateX( -8px); }
  100% { transform:translateX(  0px); }
}

@keyframes vertical {
    0% { transform:translate(-50%,-50%); }
  100% { transform:translate(-50%,-55%); }
}

摘要
・アニメーションhorizontalでまずは右左にユラユラ
・CDジャケットだけはもっとランダムにユラユラしてほしいので、中の要素.lpimgにさらに縦方向のアニメーションverticalを指定。
・CDジャケットのユラユラを少しでもランダム化するため、ループ時間をずらして設定。

3.パーティクルを飛び散らかす

このステップだけで、"ビジュアルMV"っぽさを格段とはね上げることが出来ます。
今回WebGLのアドベントカレンダーに参加させて頂いているので、ある意味この部分が本題になります。

パーティクルを飛ばすには、WebGLの力が必要不可欠です。

一切WebGL関連の技術をやったことが無かったので、かの有名なWebGLのライブラリthree.jsを使用することにしました。

以下サイト様に本当にお世話になりました。
Three.js入門サイト - ICS MEDIA

Three.jsの導入

まずはthree.jsのスクリプトを読み込み、描画領域であるCanvas要素を作成しておきます。

particle-1.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.min.js"></script>
    <title>ガイド線表示</title>
</head>
<body>
    <canvas id="myCanvas"></canvas>
</body>

ガイド線を引く

🌟DEMO1を見る

まずはパーティクルを作りやすい環境を作りましょう。
とりあえずcanvas要素ガイド線を表示させます。

index.js
window.addEventListener('load', init);

      function init(){
        // Canvas未サポート対応
        if (!window.HTMLCanvasElement) return;

        // サイズを指定
        const width = window.innerWidth;
        const height = window.innerHeight;

        // レンダラーを作成
        const renderer = new THREE.WebGLRenderer({
          alpha: true,
          canvas: document.querySelector('#myCanvas')
        });

        renderer.setClearAlpha(0);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(width, height);

        // シーンを作成
        const scene = new THREE.Scene();

        // カメラを作成
        const camera = new THREE.PerspectiveCamera(70, width / height, 1, 6000);
        camera.position.set(0, 500, 1500);
        camera.lookAt(scene.position);

        //ガイド線
        let gridHelper = new THREE.GridHelper(width, 5); // サイズ, 分割数
        scene.add(gridHelper);

        let axisHelper = new THREE.AxisHelper(width); // サイズ
        scene.add(axisHelper);

        renderer.render(scene, camera);

      }

自由に動けるカメラを作成

🌟DEMO2を見る

Three.jsにはカメラを抑制するOrbitControlsというものが存在しているらしいのですが、ライブラリ本体には含まれていないようです。

まずはHTML側で以下スクリプトを読み込むよう指定します。

particle-2.html
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>

そしてJavascriptには以下記述を追加します。

index.js
// コントロールカメラ
let controlCamera = new THREE.PerspectiveCamera(35, width / height, 1, 6000);
controlCamera.position.set(0, 500, 1600);
let controls = new THREE.OrbitControls(controlCamera);

tick();

function tick() {
renderer.render(scene, controlCamera);
requestAnimationFrame(tick);
}

ガイド線表示の時はrenderer.render(scene, camera);を指定していましたが、
今回は新しく作成したカ自由に動けるカメラでレンダリングするため、renderer.render(scene, controlCamera);としています。

これでカメラをぐりぐりと動かすことが出来ます!

ただの正方形を表示する

🌟DEMO3を見る

作りやすい環境が整ったところで、まずは簡単なオブジェクトを表示します。

index.js
let geometry = new THREE.BoxGeometry( 200, 200, 200 );
let material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
let cube = new THREE.Mesh( geometry, material );
scene.add( cube );

Three.jsにはジオメトリマテリアルというものがあります。

すごく簡単に言うと、以下になります。

ジオメトリ=「形状」
マテリアル=「表面の仕上げ方法」

例:「ジオメトリは球体で、マテリアルはマットな感じ」というイメージ。

今回は立方体なので、ジオメトリにはBoxGeometryを指定しています。
他にも、球体はSphereGeometry、平面はPlaneGeometry、ドーナツ型はTorusGeometryなどなど。。。

詳しくは以下を参照ください。

three.js-documentation

パーティクルを作成する

🌟DEMO4を見る

立方体はできました。
が、今回作成するのはパーティクル。

そもそもパーティクルとはなんだ。。
→今回は「薄っぺらい四角い紙みたいなやつ」を大量に飛び散らかすことにしました。

立方体を薄っぺらい四角い紙へ

index.js
let geometry = new THREE.PlaneGeometry( 200, 200 );
let material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} );
let plane = new THREE.Mesh( geometry, material );
scene.add( plane );

せっかく作った立方体を薄っぺらい紙へ変更しました。

パーティクルを動かす

🌟DEMO5を見る

パーティクルは飛び散らなければなりません。
まずはパーティクルを動かせるようにしましょう。

今回は下から上に散っていく、パウダースノー系パーティクルを想定しています。

index.js
tick();

function tick() {
  plane.rotation.y += 0.1;
  plane.rotation.x += 0.1;
  plane.rotation.z += 0.1;

  plane.position.x += 1;
  plane.position.y += 1;
  plane.position.z += 1;

  renderer.render(scene, controlCamera);
  requestAnimationFrame(tick);
}

座標の移動と、ジオメトリの回転を関数tickの中に組み込みました。

これをrequestAnimationFrameで延々と再描画しています。

パウダースノーとはほど遠い。。

パーティクルを量産する

🌟DEMO6を見る

パーティクルはそれなりに大量に飛び散らなくてはなりません。
ひとまず場所はランダムに、大量表示させてみます。

index.js
const X_SIZE = window.innerWidth;
const Y_SIZE = window.innerHeight;
const LENGTH = 80;
const PLANE_SCALE = 10;
const plane = [];

for(let i=0; i<LENGTH; i++){
        let geometry = new THREE.PlaneGeometry( PLANE_SCALE, PLANE_SCALE );
        let material = new THREE.MeshBasicMaterial({
                color: 'black', 
                side: THREE.DoubleSide, 
                opacity: 0.7,
                transparent: true
                });
        plane[i] = new THREE.Mesh( geometry, material );

        plane[i].position.x = X_SIZE * (Math.random() - 0.5);
        plane[i].position.y = Y_SIZE * (Math.random());
        plane[i].position.z = X_SIZE * (Math.random() - 0.5);
        scene.add( plane[i] );
}

function random(min, max) {
    let rand = Math.floor((min + (max - min + 1) * Math.random()));
    return rand;
}

tick();

function tick() {
        for(let i=0; i<LENGTH; i++){
              plane[i].rotation.y += (Math.random()*0.1);
              plane[i].rotation.x += (Math.random()*0.1);
              plane[i].rotation.z += (Math.random()*0.1);

              plane[i].position.x += (random(-5, 5)*0.1);
              plane[i].position.y += 2.5;
              plane[i].position.z += (random(-5, 5)*0.1);
         }
          renderer.render(scene, controlCamera);
          requestAnimationFrame(tick);
        }

こんな感じで何とかできました。

変数LENGTHでパーティクルの数を指定し、forループで作成したパーティクルの一つ一つを配列planeに格納。

黄色だと見づらかったのでパーティクルの色を黒色に変更し、サイズも調整しています。

関数tick内で各パーティクルの座標とジオメトリの回転を指定しています。
ランダマイズするため、Math.random()を使用しています。

「0~10の間」みたいな最小値、最大値を指定したランダマイズをしたかったので関数randomを作成し、
座標の移動に使用しています。

タイムリープループさせる

🌟DEMO7を見る

大量にできましたが、表示したそばから上に飛んでいって戻ってきません。。
次は、上がるところまで上がったらまた下に戻るようにします。

index.js
const width = window.innerWidth;
const height = window.innerHeight;

function tick() {
    for(let i=0; i<LENGTH; i++){
        plane[i].rotation.y += (Math.random()*0.1);
        plane[i].rotation.x += (Math.random()*0.1);
        plane[i].rotation.z += (Math.random()*0.1);

        plane[i].position.x += (random(-5, 5)*0.1);
        plane[i].position.y += 2.5;
        plane[i].position.z += (random(-5, 5)*0.1);

        if (plane[i].position.y > height) {
            plane[i].position.x = X_SIZE * (Math.random() - 0.5);
            plane[i].position.y = 0;
            plane[i].position.z = X_SIZE * (Math.random() - 0.5);
        }
    }
}

先ほどまでの関数tick内のループ処理に、条件分if (plane[i].position.y > height)を追加しました。

変数heightにはwindowの高さを入れています。
これでy座標がwindowの高さを超えたら0にリセットし、永遠に下から上へパーティクルが上がるようになります。

もっとランダム性が必要

🌟DEMO8を見る

今のままではただ単に下から上への移動のみ。

もっと竜巻みたいに回転したりして欲しかったのでひと工夫。
マウス位置によってカメラを回転させることで、あたかもパーティクルが舞っているかのようにしました。

index.js
// コントロールカメラ
// let controlCamera = new THREE.PerspectiveCamera(35, width / height, 1, 6000);
// controlCamera.position.set(0, 500, 1600);
// let controls = new THREE.OrbitControls(controlCamera);

let rot = 0; 
let mouseX = 0; 

document.addEventListener("mousemove", (event) => {mouseX = event.pageX;});

tick();

function tick() {

    const targetRot = (mouseX / window.innerWidth) * 360;
    rot += (targetRot - rot) * 0.01;
    const radian = rot * Math.PI / 180;

    camera.position.x = 1000 * Math.sin(radian);
    camera.position.z = 1000 * Math.cos(radian);

    camera.lookAt(new THREE.Vector3(0, 0, 0));

    for(let i=0; i<LENGTH; i++){
        plane[i].rotation.y += (Math.random()*0.1);
        plane[i].rotation.x += (Math.random()*0.1);
        plane[i].rotation.z += (Math.random()*0.1);

        plane[i].position.x += (random(-5, 5)*0.1);
        plane[i].position.y += 2.5;
        plane[i].position.z += (random(-5, 5)*0.1);

        if (plane[i].position.y > height) {
                plane[i].position.x = X_SIZE * (Math.random() - 0.5);
                plane[i].position.y = 0;
                plane[i].position.z = X_SIZE * (Math.random() - 0.5);
        }
    }

    renderer.render(scene, camera);
    requestAnimationFrame(tick);
}

}

マウス位置によってカメラ位置を変更するため、自動移動カメラとはおさらばです。
コメントアウトします。

イベントリスナーmousemoveで、マウスを移動している時のみ座標を取得するようにしています。

そして関数tickの中でマウス位置によってカメラを動的に回転させています。

これでパーティクルは完成です!

4.オーディオビジュアライザを表示させる

最後にオーディオビジュアライザです。

当初は適当に棒をアニメーションさせるだけにしようと思っていたのですが、せっかく曲を流せるんだから、リアルにオーディオビジュアライザが表示できないか考えました。

Web Audio APIの登場

ここで登場するのがWeb Audio API

Web Audio APIを使うことで、音声ファイルにエフェクト効果を追加したり、音声ファイルの波形情報を取得することができます。

Web Audio APIには音声ファイルを音源をとして利用するためのAudioNodeというものがあります。

AudioNodeは、スネア1発のような効果音系に適したAudioBufferSourceNodeと、楽曲1曲丸ごとのような長い音声ファイルに適したMediaElementAudioSourceNodeがあります。

今回は1曲丸ごとを流すことが目的のため、MediaElementAudioSourceNodeを使用していきます。

custom.js
let source, animationId;
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext;

let analyser = audioContext.createAnalyser();
analyser.fftSize = 128;
analyser.connect(audioContext.destination);

let gainNode = audioContext.createGain();
gainNode.connect(analyser); 

let audioTag = document.getElementById('usermusic');
let pausebtn = document.getElementById('mplaypause')

source = audioContext.createMediaElementSource(audioTag);
source.connect(gainNode);

function handleDragOver(e) {
  e.stopPropagation();
  e.preventDefault();
  e.dataTransfer.dropEffect = 'copy';
}

function execDrop(e) {
  e.stopPropagation();
  e.preventDefault();

  audioContext.resume().then(() => {
    console.log('Playback resumed successfully');
  });

    pausebtn.setAttribute('src' , 'img/Orion_play.png');

    let files = e.dataTransfer.files;
    let mimecheck = files[0].type;

    if (mimecheck.startsWith('audio')) {
      pausebtn.style.display = 'inline';
      document.getElementById('output').innerHTML = files[0].name;
      let audiourl = URL.createObjectURL(files[0]);
      audioTag.setAttribute('src' , audiourl);
      document.querySelector('body').style.backgroundImage = 'url(img/landscape.jpg)'; 
      document.getElementById('lpimg').setAttribute('src' , 'img/lpimg.jpg');
    }
  }

  let isPlay = false;

  pausebtn.addEventListener('click', function(event){
    if (!isPlay) {
      audioTag.play();
    pausebtn.setAttribute('src' , 'img/Orion_pause.png');
  } else {
    audioTag.pause();
    pausebtn.setAttribute('src' , 'img/Orion_play.png');
  }
  isPlay = !isPlay;
});

let canvas        = document.getElementById('visualizer');
let canvasContext = canvas.getContext('2d');
canvas.setAttribute('width', analyser.frequencyBinCount * 10);

render = function(){
  let spectrums = new Uint8Array(analyser.frequencyBinCount);
  analyser.getByteFrequencyData(spectrums);
 
  canvasContext.clearRect(0, 0, canvas.width, canvas.height);
  for(let i=0, len=spectrums.length; i<len; i++){
    canvasContext.fillStyle = 'rgba(255, 255, 255, 0.7)';
    if (i%3 === 0){
      canvasContext.fillRect(i*10, 80, 4, spectrums[i]/6);
      canvasContext.fillRect(i*10, 70, 4, 10);
      canvasContext.fillRect(i*10, 70, 4, -(spectrums[i]/6));
    } else if (i%5 === 0) {
      canvasContext.fillRect(i*10, 80, 4, spectrums[i]/2);
      canvasContext.fillRect(i*10, 70, 4, 10);
      canvasContext.fillRect(i*10, 70, 4, -(spectrums[i]/2));
    } else {
      canvasContext.fillRect(i*10, 80, 4, spectrums[i]/5);
      canvasContext.fillRect(i*10, 70, 4, 10);
      canvasContext.fillRect(i*10, 70, 4, -(spectrums[i]/5));
    }
  }
  animationId = requestAnimationFrame(render);
};

オーディオビジュアライザもcanvas要素で描画しています。

今回は、body要素にイベントハンドラondragoverondropを追加しブラウザ上に音声ファイルをドロップできるようにしています。

index.html
<body id="DropFrame" ondragover="handleDragOver(event);" ondrop="execDrop(event);">

Web Audio API開発に役立ったのがFirefoxのデベロッパーツールです。
Web Audio エディターなるものがあります。

analyser.connect(audioContext.destination);や、gainNode.connect(analyser);のように、用途ごとのノードを接続して行くのですが、この繋がりを可視化したり、そのノードをミュートしたりといった操作が可能です。

設定方法などは以下を参照ください。
Web Audio エディター

もっといろんな曲を"ビジュアルMV"化したい。

ここまでで、一応目標としていたものは形になりました。

が、どうせならもっといろんな曲を"ビジュアルMV"化したい。。

そこで、各種音楽サービスのWeb APIは活用できないか考えました。

※当初はSoundCloudを使う予定でしたが、現在SoundCloudのWeb APIサービスの新規登録はストップしているようで断念。。(2018/12/18現在)

Spotify Web API

spospo.png

ユーザー数や、曲数はもちろんですが、Web API関連も充実してそうだったので、Spotify Web APIを活用することに決めました。

※youtube musicもそのうちWeb APIを提供してくれそうな気がしますね。
(きっとプレミアム会員限定に違いない。)

結果、プレミアム(有料)会員限定の機能にはなってしまいましたがSpotifyのURLから曲を再生でき、且つジャケット画像と背景画像もURLから指定した曲のものへ変更できるようになりました。

これでどんな曲もPost-hardcore風の"ビジュアルMV"化をすることが出来ます。

クリスマスといえばこの曲、Bing Crosbyの「White Christmas」もこんなにカッコよくなります。

white christmas.png

Spotify Web APIを使う

Spotify Web APIを使うにあたって必要な手順をかなり端折って説明します。

Spotifyに登録

Spotify未登録の方は上記より登録出来ます。
ちなみに無料会員でもWeb APIの作成自体は出来るみたいです。

developerサイトにアクセスし、ログインします。

③Appを作成

developerサイトの「DASHBOARD」からアプリを作成します。
My New Appをクリックし、アプリを作成します。

クリックしたらアプリの詳細が聞かれるので、ポチポチして登録して行きましょう。

sptfy1.png

Appを作成したら以下のような画面になります。

ここで、作成したアプリのIDを確認することが出来ます。

さらに、「EDIT SETTING」をクリックしてアプリのRedirect URIなどを設定しましょう。

このIDRedirect URIは後で使用することになります。

spyfy2.png

Spotify Web API実装

以下サイト様を参考に作成しました。

Spotify Web Playback SDK Template

HTML側、</body>前とかで以下のスクリプトを読み込んでおきます。

index.html
<script src="https://sdk.scdn.co/spotify-player.js"></script>

まずはトークン発行のためのログイン画面実装です。

custom.js
window.onSpotifyWebPlaybackSDKReady = () => {

  let login_sp = document.getElementById('sptfy');
  let mtrigger = document.querySelector('#trigger');

  login_sp.addEventListener('click', function(event){

    const authEndpoint = 'https://accounts.spotify.com/authorize';
    const clientId = '285998fe3500467bb715878d0a767dbf';
    const redirectUri = 'https://m0nch1.github.io/visual-mv/';
    const scopes = [
      'streaming',
      'user-read-private',
      'user-modify-playback-state',
      'app-remote-control'
    ];

    pausebtn.setAttribute('src' , 'img/Orion_pause.png');
    pausebtn.style.display = 'inline';

    var hash;

    if (!_token){
      mtrigger.checked = false;
      hash = window.location.hash
      .substring(1)
      .split('&')
      .reduce(function (initial, item) {
        if (item) {
          let parts = item.split('=');
          initial[parts[0]] = decodeURIComponent(parts[1]);
        }
        return initial;
      }, {});
      _token = hash.access_token;
    } else {
      window.location.hash = '';
    }

    if (!_token) {
      window.location = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join('%20')}&response_type=token&show_dialog=true`;
    }
  });
}

これでSpotifyマークのボタンをクリックしたときに、
トークンが無い状態であればwindow.locationでWeb API認証の画面にリダイレクトすることが出来ます。

さらに、URLインポートボタンがクリックされた時に指定されているURLから曲の情報を取得し、プレイヤーを作成します。

custom.js
  let spbtn = document.getElementById('sp_btn');
  spbtn.addEventListener('click', function(event){

    audioTag.removeAttribute('src');
    mtrigger.checked = false;

    let spurl = document.getElementById('sp_url').value;
    spurl = 'spotify:track:' + spurl.slice(-22);

    let player = new Spotify.Player({
      name: 'particle music player🌟',
      getOAuthToken: callback => {
        callback(_token);
      },
      volume: 1
    });

    player.addListener('ready', ({ device_id }) => {
      console.log('Ready with Device ID', device_id);

      const play = ({
        spotify_uri,
        playerInstance: {
          _options: {
            getOAuthToken,
            id
          }
        }
      }) => {
        getOAuthToken(_token => {
          fetch(`https://api.spotify.com/v1/me/player/play?device_id=${id}`, {
            method: 'PUT',
            body: JSON.stringify({ uris: [spotify_uri] }),
            headers: {
              'Content-Type': 'application/json',
              'Authorization': `Bearer ${_token}`
            },
          });
        });
      };

      play({
        playerInstance: player,
        spotify_uri: spurl,
      });

    });

    player.connect();

    player.on('player_state_changed', state => {
      if (!state.paused){
        login_sp.style.display = 'none';
        animationId = requestAnimationFrame(sprender);
      } else {
        login_sp.style.display = 'inline';
        animationId = requestAnimationFrame(render);
      }
      document.getElementById('lpimg').setAttribute('src' , state.track_window.current_track.album.images[0].url);
      let srcpath = document.getElementById('lpimg').getAttribute('src');
      document.querySelector('body').style.backgroundImage = 'url(' + srcpath + ')'; 
      document.getElementById('output').innerHTML = state.track_window.current_track.name;
    });

    pausebtn.addEventListener('click', function(event){

      let statechk = pausebtn.getAttribute('src');

      if (statechk === 'img/Orion_play.png') {
        player.resume().then(() => {
          pausebtn.setAttribute('src' , 'img/Orion_pause.png');
        });
      } else {
        player.pause().then(() => {
          pausebtn.setAttribute('src' , 'img/Orion_play.png');
        });
      }
    });

  });

これで晴れて入力されたURLから曲を再生することが出来ます!

URLから再生する際の注意点

インポートするURLは「トラック」のURLでないと流れません。。
「アルバム」のURL等はインポートしても何も起きません。

楽曲のタイトル上で右クリック曲のリンクをコピーでトラックのURLを取得できます。

sptfy4.png

Web Playback SDKがスゴい

今回の使っているのは、「Web Playback SDK」というAPIです。

この仕様はbeta版のようです。

実は、ブラウザ上でブラウザのSpotifyを開いておき、そこから先ほどの要領でURLをコピーして「particle music player🌟」側で再生するとブラウザのSpotify側も動作が連動していることがわかります。

1度接続されれば、ブラウザのSpotify側で曲を変更すると「particle music player🌟」側もその曲へと変わってくれます。

official髭男dism「stand by you」のような激オシャレソングですらも、
まさにPost-hardcoreの"ビジュアルMV"みたいな感じになって素晴らしいです!!

stand by you.png

まとめ

今年、初めてアドベントカレンダーに参加させて頂きました。
いろんなナレッジを参考にしたので、コードのリファクタリングが必要な箇所が多々あります。。

WebGLとしてはthree.jsでパーティクルを飛び散らかすだけでしたが、これを機にWebGLにのめり込んでいきたいなと思います。

どうせだから音楽の波形やリズムデータなどを取得してそれに合わせて描画を変えるとか、次はやってみたいです。

Spotify Web APIも無限の可能性を感じました。

次の目標は海外系バンドによくある"リリックビデオ"をWebで再現することです。
以下、海外系バンドによくある"リリックビデオ"の例です。

Mayday Parade - "Somebody That I Used To Know" feat. Vic Fuentes (Punk Goes Pop 5)
https://youtu.be/ftE9uSPpecs

JavaScriptもっと理解しないとな。。

plus ultra!!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away