先日、Spotifyが仕様変更(ローカルなhttpでのSpotifyアプリの制御・情報取得機能の廃止)したために、対応に追われていた。というほどまじめにやっていた訳ではなかったが、それまで動いていた機能(再生位置の表示)が、ある日(7/21頃)アプリを再起動したら、突如として駄目になって慌てたのは確かだ。検索してみたら、他にも引っ掛かった方が居て、フォーラムの投稿がいくつか見つかった(例:  "[IMPORTANT] SpotifyLocalAPI not working #254")。

実は、今回のことは最初から予想していたので、「勝手なことをしやがって! ク○が!」などとは全く思っていないw 「意外に早かったな・・・」程度だ。そもそも、非公開の機能を勝手に使っていたこっちが悪いので、それを彼らが予告なしに無効にしたって、文句を言う筋合いはない。

が、再生位置の表示がないとどうにも不便なので、何とかしなければならなかった。根本的な解決策(公式のWeb APIを使うこと)は既に分かっていたのだが、資料を見るとどうにも面倒な感じなので、できれば避けたかった(前もって書くが、こういう手抜きが後々どうにもならなくなることが多い)。

それで、とっつきにくいWeb APIは後回しにして、まずは、小手先(あるいは、子供だまし)の対処をした。再生位置(時間)は分からないが、新しい曲の再生が開始されたことは分かるので、開始した時からの経過時間を表示する(要は、1秒ずつカウントアップする)ようにしてみた。見た目はそれらしいが、中身は誤魔化しもいいところだ。

それにする前に他の案も検討したのだが、どれも駄目な(手軽にはできない)感じだった。例えば以下である。

  • アプリのウインドウに表示される再生位置の数字を(プログラムで)「読む」。: 検索してもそんな便利な物はなく、無理っぽかった。やるなら、画像認識が要りそうだ。ちゃんとできればいいが、さすがに毎秒やるのは重そうだ。
  • アプリとウインドウシステム(X Window System)のサーバ間の通信を傍受する。: どれが何か判別不能だった。どうも、数字は文字でなく画像として表示している感じだった。
  • アプリのログやメモリを読む。デバッガ(gdb)でアプリの変数を読む。: やっぱりできなかった。ログには書かれていなかった。デバッガなんて、使い方を思い出せなかった・・・
  •  ブラウザでwebプレーヤーを表示して、その数字を読む。: メモリが無駄になるので、却下した。
  • アプリとSpitifyのサーバとの通信を傍受する。: やっぱり判別不能だった。TLSで暗号化しているだろうから、そもそも無理だろう。

誤魔化し策は結構簡単そうな気がしたが、実際に作ってみると意外に大変だった。まず、再生を一時停止してもカウントアップが停まらなかったしw、アプリで再生位置を変えても(プログレスバーでのジャンプ)反映されないし、アプリの起動時に曲の先頭でなければ、その曲の最後までずっとズレたままだ。

かなり試行錯誤して、一時停止とジャンプについては何とか対処できた。後者は、アプリのコンソール出力やログに情報が出る("Flush driver"の行)ので、それを使った。しかし、起動時の再生位置の反映は無理だった。そうでなくても、どうしても1-2秒の微妙なズレが出て、気分が悪い。そればかりか、この対処を入れたら、アプリどころかシステムまで不安定になってしまった。ウインドウシステムが不意に落ちてしてしまうのだ。ログを読むのにFIFOを使ったのが良くなかったのだろうか?

さすがに観念して、Web APIを使うことにして、調べた。やっぱり、認証(OAuth)が面倒だった。資料の書き方が今一つ分かりにくいのか(例えば、認証の方式が3つもある)、僕が慣れていないだけなのか、最初はどうすればいいかさっぱり分からなかったが、試行錯誤するうちに分かってきた。

確かに、Spotifyアプリの再生位置は、Web APIの"Get Information About The User's Current Playback"で取得できる。APIを使うこと自体は簡単なのだが、認証が大変だった。"Authorization Guide"を読んで、途方に暮れた。。。 が、ここで引き下がる訳にも行かないので、何とかした。もちろん、APIを使うための既存のプログラムもいくつかあるのだが、この認証が特殊なために簡単には使えなそうだったので、自分で作った。

何が特殊かというと、「古き良き」パスワードが全く使えないのだ。Spotifyにアクセスするアプリを最初に使う時は、必ず、ブラウザでSpotifyのサーバで認証(ログインやアプリのアクセスの許可)をしなければ、アクセスするための情報(トークン)が取得できないのだ。Googleのアプリパスワードような逃げ道すらない。ユーザにとっては安全でありがたいのだが、こっちの身にもなって欲しいw

ブラウザを使うこと以外に、Spotifyでの認証後に、指定したサーバにリダイレクトされる(ページがジャンプする)ので、それも何とかしなければならない。なぜかというと、そのページのURLにAPIアクセスのためのトークン(正確にはトークンを得るための情報)が入っているので、アプリはそれを取得しなくてはならないのだ。アプリを動かすのにサーバも要るのかって話で、全くやれやれだった。

更に試行錯誤するうちに、以下のことが分かった。

  • リダイレクトはPC内でローカルに済ませられる(Spotify社からアクセスされる訳ではない)ので、サーバは外部になくてもいい("localhost"は駄目なようだが、正しいドメイン名があればいいようだ)。
  • リダイレクト先のページの内容は何でもいい。ブラウザに表示されるが、(Spotifyの人でなく)自分が見るので、綺麗でなくても良く、空白だっていい。アプリがそのURLが取得できればいい。
  • そのため、リダイレクト先のページを出すサーバは、まともなwebサーバである必要はない。リダイレクト先のURLを取得できればいい。

それで、以下のように実現した。

  • リダイレクト先のURLは、自分のドメインに架空のサブドメインを付けたものにする。(例: dummy.piulento.net)
  • その架空サーバのIPアドレスは、ドメインに関係なく、宅内LANのローカルなアドレスになるようにPCに設定する(この前の宅内サーバの経験が役だった)。
  • ページを出すサーバは、認証をする時だけsocatというコマンドででっち上げる。ブラウザがトークンを含んだURLを要求して(送って)来るので、それを読んでアプリに渡し、ブラウザには、適当な応答(例: "HTTP/1.1 200 OK"と「認証に成功した」っていうメッセージ)を返す(この程度ならPHP自体でもできるが、最初から作るのも面倒なので、socatを使った。なお、ncコマンドも使えるが、ブラウザに表示できないので見苦しい)。
  • 一度認証を通せば、あとはそのトークンの有効期限が切れる頃に更新用情報を使って更新できる(この時はブラウザ不要)ので、そのための情報を保存しておく。
  • また、APIへのアクセスにはアプリ自体の認証情報(あらかじめ、Spotifyのサイトで作っておく)も必要なので、ファイルに保存しておく。
  • この処理は、本体とは別のプログラムで実現した。そのプログラムは、従来の再生情報取得処理が作るのと同じ形式(ローカルhttpでアプリから取得した情報の形式)の情報ファイルを作成するようにして、本体の変更がなるべく少なくなるようにした。

おそらく、上を読んだ方の99.9%は理解できないと思う。そんな、死ぬほど面倒なSpotifyの(OAuthの)認証ではあったが、そこを突破したら、あとはスルッと動いた。そして、結果は以下のとおりで、見た目は何も変わらないが、以前のように再生位置が表示できている。

Spotifyの仕様変更(廃止)に対処して、再び再生位置を表示できるようにした。

なお、今回作った再生情報取得プログラムは、bashでなくPHPで作った。さすがに、あの七面倒な認証をbashでやれるとは思えなかったので、テストプログラムを作る時からPHPにした。PHPにしたら、すごく作りやすかった。追って本体も書き換え(作り直し)たいが、現状の動作に大きな問題がある訳ではないので、あまりやる気が出ないw

ついでに、Web APIでは再生位置をジャンプさせることも可能なので、それを利用して、Dbusではできなかった再生停止機能(全く普通の「停止」です)も実現した。一時停止の後に曲の先頭にジャンプするだけだが、昔から欲しかったので気分がいい。

SpotifyのWeb APIにはいろいろ機能があるので、暇つぶしに遊ぶのには良さそうだ。ただ、以前も書いたように、Thumbs up/downする機能がないなど、どうも詰めが甘い感じで、そこがちょっと残念ではある。でも、GoogleやAmazonなんて、APIを公開してすらいないから、千倍はまともだと言えようw

あと、問題だと思っているのは、アプリに固有の認証情報が要るため、アプリを配布する時にはその情報が外部に取り出せないようにしなければならないが、それは至難の技なので、アプリ流通の妨げになるのではないかということだ。例えば、インストール時にブラウザでユーザに登録してもらうとかがいいのか? それもかなり面倒だが、Electronのような、ブラウザをアプリ化する仕組みなら、ユーザー登録処理に統合できそうだ。あとは、やはりアプリとブラウザが混ざっている状態を使って、アプリのインストール時にバックグラウンドで勝手に登録してしまうとかか(これは禁止されそうだが・・・)。

Spotifyだけ特別なことをしている訳ではないが、やっぱり面倒だ。

 

PS. ふと思ったが、アプリの認証情報をどうやって隠すかという問題は、アプリを配布しなければいいのだろう。Web APIはブラウザなどで動くアプリでの利用を主な対象にしているのではないか。今はそういうのが当たり前だし。ただ、僕のようにちょっと作ってみた人が配布しようとすると、かなり大変だ。

PS2. Spotifyアプリの再生状態取得APIにはどうしても気に入らない点がある。どうして、同じコンピュータの中で動いているSpotifyアプリの状態を知るのに、数千kmも先にあるであろう、Spotifyのサーバにアクセスする必要があるのだろうか?? 1回だけならまだ許せるが、再生位置をリアルタイムで知ろうとすれば、少なくとも毎秒アクセスしなくてはならない。

データ量は微々たるものだが、情報取得のために、毎回、(通信プログラムを起動して、)サーバに接続しなおすなんてオーバヘッドが大きくて、センスが悪過ぎる。しかも、アプリも状態をリアルタイムにサーバに送っているのだろうから、2倍の距離を行き来している。確かに、今は回線や機器が高速だから効率の悪さに気付くことはないが(逆に、これでまともに使えているのが不思議なくらいだ)、本質的にひどい仕様だと思う。廃止されたローカルなhttpの方がずっと良かった。この点は大いに文句を言いたい。 (21:38)

気になったので少し調べたら、Spotify自体も再生位置(時間)は「誤魔化し方式」を採用しているようだ。というのは、SpotifyアプリからSpotifyサーバへの通信が頻繁でなく間欠的だからだ(再生開始時に大量にアクセスがあるが、その後は時々ちょっと通信する程度)。もし、リアルタイムに現在の状態(再生位置など)を送信しているなら、間欠的ではおかしい。確かに、一時停止や再開した時にその情報を送信していれば、再生位置はサーバ側で適宜計算(推測)できる。

ただし、再生中に通信が途絶えたら、サーバに状態を送信できないので、再生位置はおかしくなるだろう。だが、Spotifyは基本的に、ネットに接続された状態(オンライン)での再生を前提としているので大きな問題はない。けれど、オフラインでローカルに保存された曲を再生する時は状態が送信できないから、再生位置を取得できなさそうだ。が、今はオフラインのことは考えていないから問題ない。

そうすると、最初に諦めた「誤魔化し方式」も全く駄目ではなかったようで、改良すればNW効率を改善できそうだ。具体的には、Web APIと併用すれば、問題点(起動時の位置が0でない場合)が克服できそうだ。ただ、再生位置をジャンプさせたことの検出はできるだろうか? いつものようにしつこいが、考えるのはおもしろい。 (7/25 5:55)

ちなみにSpotifyのwebプレーヤーも誤魔化し方式で、アプリでのジャンプや一時停止など、再生状態の同期には対応していない。意外にザルだったw (7/25 6:02)

上に書いたように、誤魔化し方式の改良版を実装した。再生開始時や定期的(10秒ごと)にWeb APIを使って再生位置を取得し、その後は1秒ずつ再生位置を加算していく。また、再生位置をジャンプさせた時には、再生開始時と同様にDbusにメッセージが来るので、それを契機にWeb APIでSpotifyサーバから再生位置を取得して修正する。

これにより、Spotifyサーバと通信する頻度を約1/10に減らすことができた。なお、理論的には定期的な再生位置の調整は不要なのだが、実際には誤差が蓄積して、本当の再生位置からずれることがあるので、した方がいい感じだ。ただ、Web APIで取得できる再生位置も本当に正しい保証がないので、余り意味がないのかもしれない。 (11:25) → 誤差の蓄積と思ったのはバグだったようで、定期的な再生位置の調整が不要になって、Spotifyサーバとの通信を最小限にできた。 (7/26 13:08)

  •   0
  •   0

4件のコメント

  1. Himajin:

    こんにちは。
    はい、99%の理解できない人の一人です(笑)
    内容は理解できないのですが、 Spotifyはアップデートが、
    頻繁に行われる印象がありますよね。

    私のような素人ではどのように変化したのか分からない程度の、
    小規模なアップデートが多いと思っていたのですが、
    PiuLentoさんはどんな仕組みか理解していて凄いなと思います。

    他の記事を見る限り、新しいブログも公開間近なんでしょうか。
    そちらの方も楽しみにしております。それではまた。

    •   0
    •   0
  2. PiuLento:

    ●こんにちは。僕は、Spotifyはもっと(便利な方に)変わってもいいと思ってますが、余り変わると、今回みたいに大変なので、程々が良さそうです^^

    新しいブログ(サーバ)なのですが、実は、ちょっと評価・検討した結果(https://blog.piulento.net/archives/73398)、止めて、現状を継続することにしました。余りメリットがなかったんです。

    •   0
    •   0
  3. Himajin:

    「こんばんは」の時間になってしまいました。
    確かにSpotifyには便利になる方向に変わって欲しいですよね。

    私の場合はローカルファイルの扱いについてです。
    再生出来るファイルの形式が限られているのを改善して欲しいし、
    Google Play Musicのようにクラウドにアップロードして、
    配信されている音源と同列に扱えるようにして欲しいですね。

    あと、iTunesとの同期を再び出来るようにしてもらえれば、
    (私の思う)理想に近づいてくれるんですけどね。

    長々と失礼いたしました。

    •   0
    •   0
  4. PiuLento:

    ●こんばんは。そう、Spotifyのローカルファイルは全然使い物になりませんね。フォーマットはMP3だけで、FLACすら使えないですしね。おっしゃるとおり、他の曲と一緒に扱われないと意味がないです。Googleはそのようにできていました。

    手が回らないのかやる気がないのか分からないのですが、惜しいと思います。

    •   0
    •   0

コメントを書く

名前    

メール 

URL