今回はRadikoに接続します。Radikoは接続に認証が必要になるので前回のWeb Radioとは若干違った接続方法となります。
Radiko
今回接続するRadikoのURL “https://f-radiko.smartstream.ne.jp/〜” はタイムフリー用のURLです。そのため聴いている番組が実際より約3から4分遅れています。また、タイムフリーは継続して1日3時間までという条件が有ります。このURLで3時間連続で聞いた事が無いので分かりませんが、3時間経つと落ちるのでしょうか。
Radikoを聞く(Arduino編)ー 認証と音声データ ーにRadikoへの認証と接続方法の説明が有ります。RadikoサーバからURL ”xxxxx.m3u8” を取得しaudio.connecttohost()関数を使ってサーバに繋げばRadikoを聴く事が出来ます。
Radiko接続に必要な情報は下記のコードで取得します。
Radiko_Key(bool flg)
//----------------------------------------------------------------------------------
// Connect Radiko
//----------------------------------------------------------------------------------
void Radiko_Key(bool flg){
char * auth_key = "bcd151073c03b352e1ef2fd66c32209da9ca0afa" ;
int key_length, offset;
char base64_encoded[32],token_p[32];
size_t base64_len;
const char *headerKeys[] = {"X-Radiko-AuthToken", "X-Radiko-KeyLength", "X-Radiko-KeyOffset"};
String str, _chunk_url;
int a,b,c;
WiFiClient * stream;
HTTPClient http;
uint8_t* _sound_buff;
_sound_buff = (uint8_t*)malloc(BUFF_MAX + 5);
//----------------- part1 ------------------------------------------------------------------------------
if(flg) print_msg("Connecting to Radiko", MG_COLOR);
http.begin("https://radiko.jp/v2/api/auth1");
http.addHeader("User-Agent", "esp32/4");
http.addHeader("Accept", "*/*");
http.addHeader("X-Radiko-App", "pc_html5");
http.addHeader("X-Radiko-App-Version", "0.0.1");
http.addHeader("X-Radiko-User", "dummy_user");
http.addHeader("X-Radiko-Device", "pc");
http.collectHeaders(headerKeys, 3);
str = "part1 = " + String(http.GET());
Serial.println(str);
if(flg) print_msg(str, MG_COLOR);
offset = String(http.header(headerKeys[2])).toInt();
key_length = String(http.header(headerKeys[1])).toInt();
_auth_token = http.header(headerKeys[0]);
http.end();
token_p[0] = '\0';
strncpy(token_p, &auth_key[offset], key_length);
token_p[key_length] = '\0';
mbedtls_base64_encode((unsigned char *) base64_encoded, sizeof(base64_encoded),&base64_len, (unsigned char *) token_p, strlen(token_p));
//----------------- part2 ------------------------------------------------------------------------------
http.begin("https://radiko.jp/v2/api/auth2");
http.addHeader("X-Radiko-AuthToken", _auth_token);
http.addHeader("X-Radiko-Partialkey", base64_encoded);
http.addHeader("X-Radiko-User", "dummy_user");
http.addHeader("X-Radiko-Device", "pc");
str = "part2 = " + String(http.GET());
Serial.println(str);
if(flg) print_msg(str, MG_COLOR);
str = http.getString();
str = str.substring(0,str.indexOf(","));
http.end();
//----------------- part3 ------------------------------------------------------------------------------
// http.begin("https://radiko.jp/v2/station/list/[ID].xml");
str = "https://radiko.jp/v2/station/list/" + str + ".xml";
http.begin(str);
http.addHeader("X-Radiko-AuthToken", _auth_token);
str = "part3 = " + String(http.GET());
Serial.println(str);
if(flg) print_msg(str, MG_COLOR);
b = http.getSize();
c = 0;
stream = http.getStreamPtr();
while(b){
while(!(a = stream->available())) NOP(); // delay(5);
stream -> readBytes(&_sound_buff[c], a);
b -= a;
c += a;
}
_sound_buff[c] = 0;
str = String((char *)_sound_buff);
b = 0;
st_list[0] = st_list[1] = "";
while((a = str.indexOf("<id>")) != -1){
a += 4;
str = &str[a];
a = str.indexOf("</id>");
st_list[0] += str.substring(0,a);
st_list[0] += "\n";
a = a + 16;
str = &str[a];
a = str.indexOf("</name>");
st_list[1] += str.substring(0,a);
st_list[1] += "\n";
b ++;
}
pl_st.st_max = b;
for(a = 0; a < b; a ++)
Serial.println("No." + String(a) +" ID: " + get_st_name(a, st_list[0]) + " , name: " + get_st_name(a, st_list[1]));
str = "station_MAX = " + String(b);
Serial.println(str);
if(flg) print_msg(str, MG_COLOR);
http.end();
free(_sound_buff);
}
この関数を実行すると AuthToken、ラジオ局のID、ラジオ局名を取得出来ます。AuthTokenはRadikoサーバーにアクセスする場合の認証キー。今回、ラジオIDと局名は下記の様になりました。
ID & Name
No. ID ラジオ局名
-----------------------------------------
1: TBS TBSラジオ
2: QRR 文化放送
3: LFR ニッポン放送
4: RN1 ラジオNIKKEI第1
5: RN2 ラジオNIKKEI第2
6: INT interfm
7: FMT TOKYO FM
8: FMJ J-WAVE
9: JORF ラジオ日本
10: BAYFM78 bayfm78
11: NACK5 NACK5
12: YFM FMヨコハマ
13: IBS LuckyFM 茨城放送
14: HOUSOU-DAIGAKU 放送大学
15: JOAK NHKラジオ第1(東京)
16: JOAK-FM NHK-FM(東京)
ラジオIDで検索用文字列st_list[0]を、ラジオ局名でst_list[1]を作成し前回同様に選択に使用します。ラジオIDとAuthTokenを持ってRadikoのサーバーにアクセスすれば、サーバから”xxxxx.m3u8”を取得出来ます。
認証キーが曲者
”xxxxx.m3u8”を引数に、関数audio.connecttohost()を使えRadikoが聴けるはずだったのですが、audio.connecttohost()でRadikoサーバに接続する場合も認証キーが必要になります。ヘッダーに”X-Radiko-AuthToken: 認証キー”を追加する必要が有り、connecttohost()関数その機能が無いか調べた所、その様な機能は無かったのですがユーザIDとPassWordを送る機能が有ることが分かりました。
bool Audio::connecttohost(const char* host, const char* user, const char* pwd)
これを使ってconst char* userを ”-“ と指定した場合、 const char* pwd(これを認証キーとする)を ヘッダーに”X-Radiko-AuthToken:pwd とセットする様ライブラリーの”Audio.ccp”を書き換えました。下記がそれです。”Audio.ccp”のバージョンにより変更箇所が若干違うと思いますが、同様の箇所を修正下さい。
Audio.ccp
bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { // user and pwd for authentification only, can be empty
bool res = false; // return value
char* c_host = NULL; // copy of host
uint16_t lenHost = 0; // length of hostname
uint16_t port = 0; // port number
uint16_t authLen = 0; // length of authorization
int16_t pos_slash = 0; // position of "/" in hostname
int16_t pos_colon = 0; // position of ":" in hostname
int16_t pos_ampersand = 0; // position of "&" in hostname
uint32_t timestamp = 0; // timeout surveillance
uint16_t hostwoext_begin = 0;
// char* authorization = NULL; // authorization
char* rqh = NULL; // request header
char* toEncode = NULL; // temporary memory for base64 encoding
char* h_host = NULL;
// https://edge.live.mp3.mdn.newmedia.nacamar.net:8000/ps-charivariwb/livestream.mp3;&user=ps-charivariwb;&pwd=ps-charivariwb-------
// | | | | |
// | | | | | (query string)
// ssl?| |<-----host without extension-------->|port|<----- --extension----------->|<-first parameter->|<-second parameter->.......
xSemaphoreTakeRecursive(mutex_playAudioData, 0.3 * configTICK_RATE_HZ);
// optional basic authorization
if(user && pwd) authLen = strlen(user) + strlen(pwd);
char authorization[base64_encode_expected_len(authLen + 1) + 1];
authorization[0] = '\0';
if(authLen > 0) {
char toEncode[authLen + 4];
strcpy(toEncode, user);
strcat(toEncode, ":");
strcat(toEncode, pwd);
b64encode((const char*)toEncode, strlen(toEncode), authorization);
}
if (host == NULL) { AUDIO_INFO("Hostaddress is empty"); stopSong(); goto exit;}
if (strlen(host) > 2048) { AUDIO_INFO("Hostaddress is too long"); stopSong(); goto exit;} // max length in Chrome DevTools
c_host = x_ps_strdup(host); // make a copy
h_host = urlencode(c_host, true);
trim(h_host); // remove leading and trailing spaces
lenHost = strlen(h_host);
if(!startsWith(h_host, "http")) { AUDIO_INFO("Hostaddress is not valid"); stopSong(); goto exit;}
if(startsWith(h_host, "https")) {m_f_ssl = true; hostwoext_begin = 8; port = 443;}
else {m_f_ssl = false; hostwoext_begin = 7; port = 80;}
// In the URL there may be an extension, like noisefm.ru:8000/play.m3u&t=.m3u
pos_slash = indexOf(h_host, "/", 10); // position of "/" in hostname
pos_colon = indexOf(h_host, ":", 10); if(isalpha(c_host[pos_colon + 1])) pos_colon = -1; // no portnumber follows
pos_ampersand = indexOf(h_host, "&", 10); // position of "&" in hostname
if(pos_slash > 0) h_host[pos_slash] = '\0';
if((pos_colon > 0) && ((pos_ampersand == -1) || (pos_ampersand > pos_colon))) {
port = atoi(c_host + pos_colon + 1); // Get portnumber as integer
h_host[pos_colon] = '\0';
}
setDefaults();
rqh = x_ps_calloc(lenHost + strlen(authorization) + 300, 1); // http request header
if(!rqh) {AUDIO_INFO("out of memory"); stopSong(); goto exit;}
strcat(rqh, "GET /");
if(pos_slash > 0){ strcat(rqh, h_host + pos_slash + 1);}
strcat(rqh, " HTTP/1.1\r\n");
strcat(rqh, "Host: ");
strcat(rqh, h_host + hostwoext_begin);
strcat(rqh, "\r\n");
strcat(rqh, "Icy-MetaData:1\r\n");
strcat(rqh, "Icy-MetaData:2\r\n");
strcat(rqh, "Accept:*/*\r\n");
strcat(rqh, "User-Agent: VLC/3.0.21 LibVLC/3.0.21\r\n");
if(authLen > 0) { strcat(rqh, "Authorization: Basic ");
strcat(rqh, authorization);
strcat(rqh, "\r\n"); }
strcat(rqh, "Accept-Encoding: identity;q=1,*;q=0\r\n");
strcat(rqh, "Connection: keep-alive\r\n\r\n");
if(m_f_ssl) { _client = static_cast(&clientsecure);}
else { _client = static_cast(&client); }
timestamp = millis();
_client->setTimeout(m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms);
- 459から468行: ユーザIDとPassWordがBase64変換されauthorization配列に代入される。
- 495行: ヘッダー用配列を定義
- 508から510行: ここで、 authorizationがヘッダーに追加される。
そこで、
- 495行: ヘッダー用配列を念の為 200増やす
- 508から518行: ユーザーIDに”ー”が指定された場合、ヘッダーに
"X-Radiko-AuthToken: pwd"が追加され
、
それ以外は通常通り。
rqh = x_ps_calloc(lenHost + strlen(authorization) + 300 + 200, 1); // http request header
if(!rqh) {AUDIO_INFO("out of memory"); stopSong(); goto exit;}
strcat(rqh, "GET /");
if(pos_slash > 0){ strcat(rqh, h_host + pos_slash + 1);}
strcat(rqh, " HTTP/1.1\r\n");
strcat(rqh, "Host: ");
strcat(rqh, h_host + hostwoext_begin);
strcat(rqh, "\r\n");
strcat(rqh, "Icy-MetaData:1\r\n");
strcat(rqh, "Icy-MetaData:2\r\n");
strcat(rqh, "Accept:*/*\r\n");
strcat(rqh, "User-Agent: VLC/3.0.21 LibVLC/3.0.21\r\n");
if(authLen > 0) {
if(user == "-"){
strcat(rqh, "X-Radiko-AuthToken: ");
strcat(rqh, pwd);
}
else{
strcat(rqh, "Authorization: Basic ");
strcat(rqh, authorization);}
}
strcat(rqh, "\r\n");
}
strcat(rqh, "Accept-Encoding: identity;q=1,*;q=0\r\n");
strcat(rqh, "Connection: keep-alive\r\n\r\n");
if(m_f_ssl) { _client = static_cast(&clientsecure);}
else { _client = static_cast(&client); }
timestamp = millis();
_client->setTimeout(m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms);
これで、audio.connecttohost(“radiko_URL”, “-“, “認証番号”)でRadikoにアクセス出来ます。
実行
今回のスケッチをここに保存しましたー> player2.ino
いつもの通り、SDカードにフォントと”url”ファイルを下記の様に保存して下さい。フォント:genshin-regular-20pt.vlw
。 ”url.txt”ファイル:url.txt。

最後に、player2.inoに有るSSIDとPASSWORDを入力してコンパイル実行して下さい。

- Web Radio(左)前回製作、Radiko(中央)今回製作、Player(右)次回製作予定 を選択する画面です。
真ん中のICON Radiko にタッチします。 - これがRadikoの初期画面です。受信出来るラジオ局の最初のラジオ局がデフォルトで選ばれる設定になっています。この画面でTBSラジオにタッチ
- ラジオ局選択画面が表示されます。前回のWeb Radioと同じ操作で聞きたいラジオ局を選択します。
- 例えば、J-WAVEを選んで OKボタンにタッチ
- 初期画面に戻ります。ラジオ局表示欄にJ-WAVEが表示されています。
- スタートボタンにタッチすればJ-WAVEが聞けます。
- 初期画面に有る3つのボタンで右のボタンにタッチすれば、機器選択画面が表示されます。現在、Web RadioとRadikoは機能します。切り替えて動きを確認下さい。
次回は
次回はSDカードに保存した音楽ファイルを再生する機能を追加して行きます。