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

ElectronでSoundCloud SDKを使う

More than 3 years have passed since last update.

Wantedly Advent Calendar 2015の15日目です!

最近Electronをやりたくて、SoundCloudクライアントを作り始めました。

認証~SDKの初期化しか作っていない絶賛WIPですが、ElectronでSoundCloud SDKを使うまでのハマリポイントをまとめます!

shimpeiws/sc-fav

TL;DR

  • ElectronでSoundCloud SDKを使うときハマったのは 認証SDKの初期化
    • 認証は通常のWebブラウザ同様にはいかないので、 Electronのprotocol APIを使う
    • SDKの初期化は レンダラープロセス側でやる

SouncCloud SDK

SoundCloudはAPIが公開されていて、JavaScript用のSDKも用意されています。APIをラップしたインタフェースになっていて、Promiseが返ってくるいい感じのSDKです。

SoundCloud JavaScript SDK

ただこのSDK、普通のWebブラウザでの使用を前提にしているので、Electronで使うのには、認証・SDKの初期化でハマりました...

SoudCloud SDK or 自前実装

ちなみに、Electron製のSoundCloudクライアントとしてgillesdemey/Cumulusがあるのですが、こちらはSDKは使わず、自前でAPIラッパーを書いています。

どちらを選ぶかはあなた次第...

認証

Webブラウザでの流れ

SoundCloud APIの認証は通常、↓の流れになります。

  1. SDKを初期化、新規ウィンドウが開く
  2. 開かれたウィンドウ内で、SDK初期化時に設定したコールバックURLにリダイレクト
  3. 2のウィンドウからTokenを取得する

HTTP API Guideによると、2のコールバック先に↓のようなhtmlをおいておく、ということになっています。

callback.html
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Connect with SoundCloud</title>
  </head>
  <body onload="window.opener.setTimeout(window.opener.SC.connectCallback, 1)">
    <b style="width: 100%; text-align: center;">This popup should automatically close in a few seconds</b>


</body></html>

window.opener.SC.connectCallbackがキモなんですが、ElectronのBrowserWindowから開くと、 window.openerがnullになるのです...

調べてみると、Electronのissueにコメントが...、バグなのか?

スクリーンショット 2015-12-13 23.31.11.png

Electronのprotocol APIを使う

普通なブラウザ同様の流れは使えないので、APIからのコールバックを Electronのprotocol APIでインターセプトすることで、APIからのコールバックをElectronでうけとります。

electron/docs/api/protocol.md

↓のように実装して、APIのコールバック先をsc-fav://oauth/callbackで指定しておくと、コールバックを受け取れるようになります。

auth.js
setupProtocol() {
    let protocol = require('protocol');
    protocol.registerHttpProtocol('sc-fav', (req) => {
        let uri = url.parse(req.url);

        switch(uri.host) {
        case 'oauth':
            if (uri.pathname !== '/callback'){
                return;
            }

            let hash = uri.hash.substr(1);
            // トークン取得
            let token = querystring.parse(hash).access_token;
            // TODO: ログイン処理
            console.log('token', token);

            break;
        default:

        }
    });
}

この部分はCumulusの実装を参考にしました。

SDKの初期化

API Documentationを見ながら実装します。

SDKの初期化はこんなインタフェース

SoundCloudSDK.initialize(
  {
    client_id: 'CLIENT_ID',
    redirect_uri: 'http://example.com/', //オプション項目
    oauth_token: 'TOKEN' // オプション項目
  }
);

'getUserMedia' of undefined

認証をElectronのメインプロセス(Node.jsのほう)でやったので、SDKの初期化もメインプロセスでやろうとすると、SDKから"'getUserMedia' of undefined"のエラーが...

スクリーンショット_2015-12-12_10_22_38_のコピー.png

SDK内の該当箇所を見ると、音声録音ができる機能があるので、navigator.getUserMediaでデバイスへのアクセスを求めるところがあるよう。メインプロセスの方では、navigatorがとれないので、undefinedになる様子...

認証からの流れ

悩んだ挙句、以下の流れで実装することに

  1. 認証はメインプロセスで行う
  2. SDKの初期化はレンダラーブロセス(BrowserWindow側)で行う
  3. 認証で取得したOAuthのトークンは IPC(プロセス間通信)でメインプロセスからレンダラープロセスに渡す

メインプロセス

window_manager.js
// ログイン完了でTokenが帰ってきた後のコールバック
loginCallback: () => {
    this.mainWindow = new BrowserWindow({ width: 350, height: 640 });
    this.mainWindow.on('closed', () => {
        app.quit();
    });
    // 画面の読み込み完了を待つ
    this.mainWindow.webContents.on('did-finish-load', () => {
        this.auth.getToken().then((token) => {
            // IPCでトークンを送信
            this.mainWindow.webContents.send('token', token);
        });
    });
    this.mainWindow.loadURL(`file://${__dirname}/..${options.indexPath}`);
}

レンダラープロセス

bootstrap.js
import React from 'react';
import ReactDom from 'react-dom';
import {Main} from './components/main';
import electron from 'electron';
import SoundCloud from 'soundcloud';

electron.ipcRenderer.on('token', (event, token) => {
    event.sender.send('token', 'success');  // 送信元へレスポンスを返す
    SoundCloud.initialize({
        client_id: 'CLIENT-ID',
        oauth_token: token
    });

    SC.get('/me/favorites').then(function(tracks){
        console.log('Latest track: ' + tracks[0].title);
    });

    SoundCloud.stream('/tracks/293').then((player) => {
        player.play();
    });
});

まとめ

認証~SDKの初期化さえできれば、曲の再生、フォローの取得やLikeなど、一通りの動作が簡単に実装できるようになります。

SoundCloudユーザの方は、Electronでクライアントを作ってみてはいかがでしょう?

参考

HTTP API Guide

SoundCloud JavaScript SDK

日本語訳 Electron(旧atom-shell) Quick start

Electronでipcを使ってプロセス間通信を行う

electron/docs/api/web-contents.md

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