この記事はtraP新歓ブログリレー2024 38日目の記事です。
こんにちは@d_etteiu8383です。@eyemono.moeでもあります。
本記事ではSpotifyのWeb Playback SDKを使用したプレイヤーの自作方法について解説します。
Spotifyとは
Spotifyはスマートフォン/PCで利用可能な音楽ストリーミングサービスです。
Spotify API
Spotifyには楽曲の検索やメタデータ(アーティスト情報、アルバム情報など)の取得などが可能なWeb APIが提供されています。
↓過去に本ブログでSpotify APIを使用した記事が投稿されています。ぜひご覧ください↓
実はこのAPIでは、楽曲情報の取得のみならず、楽曲の再生制御も可能です。しかしこれを使用したプレイヤーを作る場合、楽曲の再生/停止等の操作をいちいちAPIリクエストで行う必要があります...
Web Playback SDK
そこでSpotifyはWeb Playback SDKを提供しています。これを使用することで、Web上でSpotifyの楽曲を再生するプレイヤーを簡単に作成することができます。
仕組みとしては、SDKを通してローカルのSpotify Connectデバイスが作成され、このデバイスで楽曲の再生制御を行うことができるようになっているらしいです。PCのSpotifyアプリを開きながら、同一ネットワーク上のスマホのSpotifyアプリから再生操作を行うと、PCのSpotifyアプリで再生が行われるのと同じような仕組みです。
この画像における「iPhoneで再生中」の"iPhone"に当たる部分を、Web Playback SDKを使用して作成することができます。
本記事ではこのWeb Playback SDKを使用したプレイヤーの作成方法について簡単に解説します。
Web Playback SDKを使用したプレイヤーの作成方法
「解説します」と言っていますが、実は公式の解説が非常に充実しているため、英語が読める方であれば以下の公式ドキュメントを読むことを強くお勧めします。
本記事では Vite + SolidJS の構成による、静的なWebページとしてプレイヤーを作成する方法を解説します。React...?なんですかそれは......?
リポジトリ:https://github.com/eyemono-moe/oreore-spotify
本記事内では大まかに何をやっているかの説明にとどめます。詳細なコードはリポジトリを参照してください。
必要なもの
- Spotifyのプレミアム会員アカウント
- Web Playback SDKを使用した楽曲の再生制御には、プレミアム会員アカウントが必要です。
- Node.js
1. Spotify OAuthアプリケーションの登録
Web Playback SDKを使用するには、SpotifyのOAuthアクセストークンが必要です。そのため、まずはSpotifyの開発者ダッシュボードからアプリケーションを登録します。
実際にSpotifyのOAuthアプリケーションを作成・公開する場合、規約やデザインガイドラインに沿ったアプリケーションを作成する必要があります。Spotidyのロゴの使用に関するガイドラインはもちろん、Spotify上に存在する楽曲データやアルバムジャケット画像の扱いについても注意が必要です。(ジャケット画像を切り抜いたりオーバーレイをかけてはいけない、楽曲データを加工してはいけないなど)
OAuthアプリケーションを作成した直後は、アプリケーションはdevelopment modeとして作成され、開発者自身のSpotifyアカウント(と開発者が設定から追加したアカウント)でのみ使用可能です。
アプリケーションを公開し、全世界のSpotifyユーザーに使用可能にするには、Spotifyの審査を通過する必要があります。
詳細:https://developer.spotify.com/documentation/web-api/concepts/quota-modes
今回の記事内ではアプリケーションの公開まではせず、ローカルサーバー上でのみ動作するアプリケーションを作成します。
2. プロジェクトの作成
プロジェクトを作成します。
pnpm create vite my-spotify
cd my-spotify
私のリポジトリではpnpm, SolidJSを使用していますが、他のパッケージマネージャやフレームワークでも問題ありません。そもそもViteでなくてもいい。
3. ログイン機能実装とアクセストークンの取得
以下のように
- https://accounts.spotify.com/authorizeに必要情報を付けてリダイレクトしてログイン画面を表示
- ログイン後、リダイレクトされたURLにcodeが付与されるので、このcodeを使ってアクセストークンを取得
- アクセストークンを保存
します。SolidJSでの例はhttps://github.com/eyemono-moe/oreore-spotify/blob/235b16325a7eda29614ac68650573fac182a834a/src/context/auth.tsxにあります。
今回は簡単のためにアクセストークンをlocalStorageに保存していますが、実際のアプリケーションではセキュリティ上の理由からlocalStorageに保存することは避けるべきです。
適当なバックエンドサーバーを用意し、sessionを使ってアクセストークンを保存するようにしましょう。
でもわざわざBFF用意するの面倒だよね。
作例のリポジトリではこのアクセストークンを使用し、Spotify APIを叩いてログインユーザーの名前を表示するようにしています。このAPIのクライアントはhttps://github.com/sonallux/spotify-web-apiで有志が作成しているOpenAPI定義から生成しています。
4. Web Playback SDKの読み込み
アクセストークンを取得できたので、さっそくWeb Playback SDKを使用しましょう。
やることは以下の4つです。
- sdkのスクリプトの読み込み
- playerの作成
- イベントリスナーの登録
- 公式クライアントへのplayerの接続
4.1 sdkのスクリプトの読み込み
以下のようにscriptタグを使ってsdkのスクリプトを読み込みます。
<script src="https://sdk.scdn.co/spotify-player.js"></script>
動的にやるなら以下のような感じ。
4.2 playerの作成
以下のようにplayerを作成します。Spotify
オブジェクトはsdkのスクリプトを読み込んだ後に使用可能になるグローバルオブジェクトです。SDKが正常に読み込まれるとonSpotifyWebPlaybackSDKReady
が自動的に呼ばれるので、その中でplayerを作成します。
window.onSpotifyWebPlaybackSDKReady = () => {
const token = '[My access token]';
const player = new Spotify.Player({
name: 'Web Playback SDK Quick Start Player',
getOAuthToken: cb => { cb(token); },
});
}
4.3 イベントリスナーの登録
作成したplayerは接続完了時やエラー時にイベントを発火します。
UIの更新やロギングを行いたい場合これらを使用しましょう。
利用可能なイベント一覧はhttps://developer.spotify.com/documentation/web-playback-sdk/reference#eventsを参照してください。
↓使用例↓
4.4 公式クライアントへのplayerの接続
作成したplayerについて、player.connect()
を実行することで、初期化が行われます。この時点で公式クライアントのデバイス一覧に自作のplayerが表示されるようになります。
ここで重要なのが、connect()
をしただけではブラウザ上での再生は可能になりません。実際に再生制御をこのplayerから行うには、再生中のデバイスをこのplayerに切り替える必要があります。
そのためにSpotify APIのTransfer Playback endpointを使用します。player.connect()
を実行するとそのplyerのデバイスIDが取得可能になるため、このデバイスIDを使用してTransfer Playback endpointを叩きます。
これで、自作のplayerからSpotifyの楽曲を再生することができるようになります🎉
5. 楽曲データ取得と再生制御
playerを作成・接続すると、player_state_changed
イベントを通じてplayback stateを取得することができます。これは以下のようなデータで構成されています(長いので一部省略しています)。
{
"playbackState": {
"timestamp": 1713083615136,
"context": {
"uri": "spotify:user:l1mq96khn4z1fg6smsp5qdyjb:collection",
"metadata": {}
},
"duration": 225570,
"paused": false,
"shuffle": true,
"position": 0,
"loading": false,
"repeat_mode": 1,
"track_window": {
"current_track": {
"id": "2vpIVi4TxAtjfF7kz9wmOQ",
"uri": "spotify:track:2vpIVi4TxAtjfF7kz9wmOQ",
"type": "track",
"uid": "f2d9c29c6320776ca5ed",
"linked_from": {
"uri": null,
"id": null
},
"media_type": "audio",
"track_type": "audio",
"name": "走れ!うさかめ高校テニス部!!",
"duration_ms": 225570,
"artists": [
{
"name": "うさかめ高校テニス部",
"uri": "spotify:artist:0Kw2qZ1BrMugNYlExzvcqm",
"url": "https://api.spotify.com/v1/artists/0Kw2qZ1BrMugNYlExzvcqm"
},
{
"name": "中島由貴(田中きなこCV)",
"uri": "spotify:artist:7oFIPeQya2BSX4zCrXlte8",
"url": "https://api.spotify.com/v1/artists/7oFIPeQya2BSX4zCrXlte8"
},
{
"name": "小出ひかる(佐藤くるみCV)",
"uri": "spotify:artist:5z9cjipgVytd6UULrUIzUv",
"url": "https://api.spotify.com/v1/artists/5z9cjipgVytd6UULrUIzUv"
},
{
"name": "新井田いづみ(鈴木あやこCV)",
"uri": "spotify:artist:4mCaN6AiX65V00yZN8NCTg",
"url": "https://api.spotify.com/v1/artists/4mCaN6AiX65V00yZN8NCTg"
},
{
"name": "谷尻まりあ(西新井大師西CV)",
"uri": "spotify:artist:5wml1Y1J3rBwBwZxgfRgIB",
"url": "https://api.spotify.com/v1/artists/5wml1Y1J3rBwBwZxgfRgIB"
}
],
"album": {
"name": "てーきゅうBEST",
"uri": "spotify:album:19OuaVZecS5k2SUHwiRDVs",
"images": [
{
"url": "https://i.scdn.co/image/ab67616d00001e02bcfb45e76a0d756da3c473a4",
"height": 300,
"width": 300,
"size": "UNKNOWN"
},
{
"url": "https://i.scdn.co/image/ab67616d00004851bcfb45e76a0d756da3c473a4",
"height": 64,
"width": 64,
"size": "SMALL"
},
{
"url": "https://i.scdn.co/image/ab67616d0000b273bcfb45e76a0d756da3c473a4",
"height": 640,
"width": 640,
"size": "LARGE"
}
]
},
"is_playable": true,
"metadata": {}
},
"next_tracks": [
{
"id": "0IiaRSmULwHbmU1rxMvFSe",
"uri": "spotify:track:0IiaRSmULwHbmU1rxMvFSe",
"type": "track",
"uid": "533d961356b613fac6b6",
"linked_from": {
"uri": null,
"id": null
},
"media_type": "video",
"track_type": "video",
"name": "白金ディスコ",
"duration_ms": 256573,
"artists": [
{
"name": "物語シリーズ",
"uri": "spotify:artist:0NT8fqhPoKJrd038u1Qumz",
"url": "https://api.spotify.com/v1/artists/0NT8fqhPoKJrd038u1Qumz"
}
],
"album": {
"name": "歌物語 Special Edition",
"uri": "spotify:album:1oP65KKl98hRSjJvpKeFmQ",
"images": [
{
"url": "https://i.scdn.co/image/ab67616d00001e02426fff6f6d9dbdd9a1bc3133",
"height": 300,
"width": 300,
"size": "UNKNOWN"
},
{
"url": "https://i.scdn.co/image/ab67616d00004851426fff6f6d9dbdd9a1bc3133",
"height": 64,
"width": 64,
"size": "SMALL"
},
{
"url": "https://i.scdn.co/image/ab67616d0000b273426fff6f6d9dbdd9a1bc3133",
"height": 640,
"width": 640,
"size": "LARGE"
}
]
},
"is_playable": true,
"metadata": {}
},
...
],
"previous_tracks": [
{
"id": "65Pu62l9hucWHs3azbKYGU",
"uri": "spotify:track:65Pu62l9hucWHs3azbKYGU",
"type": "track",
"uid": "31b52dc6e108d8855063",
"linked_from": {
"uri": null,
"id": null
},
"media_type": "video",
"track_type": "video",
"name": "ドリームパレード",
"duration_ms": 155946,
"artists": [
{
"name": "ふれんど〜る(cv.茜屋日海夏&芹澤 優&澁谷梓希&牧野由依&渡部優衣)",
"uri": "spotify:artist:0dippPmLv2DuHuNSi5dIVn",
"url": "https://api.spotify.com/v1/artists/0dippPmLv2DuHuNSi5dIVn"
}
],
"album": {
"name": "プリパラ☆ミュージックコレクション season.2",
"uri": "spotify:album:4KJnTeFK500L0eRyvdVfjy",
"images": [
{
"url": "https://i.scdn.co/image/ab67616d00001e0216683ee2672611d60129a78c",
"height": 300,
"width": 300,
"size": "UNKNOWN"
},
{
"url": "https://i.scdn.co/image/ab67616d0000485116683ee2672611d60129a78c",
"height": 64,
"width": 64,
"size": "SMALL"
},
{
"url": "https://i.scdn.co/image/ab67616d0000b27316683ee2672611d60129a78c",
"height": 640,
"width": 640,
"size": "LARGE"
}
]
},
"is_playable": true,
"metadata": {}
},
...
]
},
"restrictions": {
"disallow_seeking_reasons": [],
"disallow_skipping_next_reasons": [],
"disallow_skipping_prev_reasons": [],
"disallow_toggling_repeat_context_reasons": [],
"disallow_toggling_repeat_track_reasons": [],
"disallow_toggling_shuffle_reasons": [],
"disallow_peeking_next_reasons": [],
"disallow_peeking_prev_reasons": [],
"undefined": [
"not_supported_by_device",
"not_supported_by_content_type"
],
"disallow_resuming_reasons": [
"not_paused"
]
},
"disallows": {
"seeking": false,
"skipping_next": false,
"skipping_prev": false,
"toggling_repeat_context": false,
"toggling_repeat_track": false,
"toggling_shuffle": false,
"peeking_next": false,
"peeking_prev": false,
"undefined": true,
"resuming": true
},
"playback_id": "62d373e704224da0bb404e3dc910de3e",
"playback_quality": "VERY_HIGH",
"playback_features": {
"hifi_status": "NONE",
"playback_speed": {
"current": 1,
"selected": 1,
"restricted": true
},
"signal_ids": []
},
"playback_speed": 1
},
"player": {
"_options": {
"name": "オレオレクライアント",
"volume": 1
},
"_eventListeners": {
"account_error": [
null
],
"authentication_error": [
null
],
"autoplay_failed": [],
"playback_error": [
null
],
"initialization_error": [
null
],
"ready": [
null
],
"not_ready": [
null
],
"player_state_changed": [
null
],
"progress": []
},
"_connectionRequests": {},
"_getCurrentStateRequests": {},
"_getVolumeRequests": {},
"_messageHandlers": {},
"isLoaded": {}
},
"errors": null,
"device": {
"device_id": "278a6c20000d406de577c59aa820f7e8870d733d",
"isReady": true
}
}
これらの情報からUIの更新を行いましょう。
また、再生制御を行う場合は以下のようにplayer
オブジェクトのメソッドを使用します。
player.setVolume(0.5).then(() => {
console.log('Volume updated!');
});
player.pause().then(() => {
console.log('Paused!');
});
player.resume().then(() => {
console.log('Resumed!');
});
player.togglePlay().then(() => {
console.log('Toggled playback!');
});
// Seek to a minute into the track
player.seek(60 * 1000).then(() => {
console.log('Changed position!');
});
player.previousTrack().then(() => {
console.log('Set to previous track!');
});
player.nextTrack().then(() => {
console.log('Skipped to next track!');
});
特定のアルバムやプレイリストの楽曲を指定して再生を開始する場合は、Playback SDKではなくAPIのStart/Resume Playbackを使用する必要があるようです。
Spotify上での複数楽曲の再生はcontextを指定して行われており、例えば"アルバムのcontext uri"や"プレイリストのcontext uri"を指定することで、そのアルバムやプレイリストの楽曲を再生することができます。そのcontext内のどの楽曲から再生するかはoffset
パラメータで指定します。
ということでSpotifyの楽曲を再生するプレイヤーが完成しました🎉
まとめ
Web Playback SDKを使用することで、Spotifyの楽曲を再生するプレイヤーを簡単に作成することができます。皆さんもぜひオリジナルのプレイヤーを作成してみてください。
おまけ
もともとは、iTunesと同様に、Spotifyで再生中の楽曲のジャケット画像を常に最前面に表示したいと思い自作プレイヤーの作成を進めていました。
↑ピクチャーインピクチャーを使用した自作プレイヤー(バグり散らかしている)
しかし、僕が気づいていなかっただけで、ブラウザ版のSpotify公式プレイヤーではとっくにこの機能が実装されていました...
しかもつい先日、ネイティブアプリ版のSpotifyにもこの機能が追加されていました。ありがとうね...
最後までお読みいただきありがとうございました。明日の新歓ブログリレー2024担当者は@cp20です。楽しみ~