Commit d685a462 authored by Kempe's avatar Kempe
Browse files

[FEATURE] Update Radio.net API to v2

* Update for new endpoint
* Fixes api language selection
parent 7f2b702c
......@@ -10,7 +10,11 @@ Python {
addImportPath(Qt.resolvedUrl('./python'));
setHandler('log', function(msg) {
console.log("Pyton log:", msg)
console.log("Python log:", msg)
});
setHandler('debug', function(msg) {
console.debug("Python debug:", msg)
});
importModule('api', function () {
......@@ -27,10 +31,10 @@ Python {
function playStationById(id) {
loading = true;
call('api.radio.getStationById', [''+id], function(response) {
var stationData = Rad.getStationFromRadioJson(response);
console.log("Playing station:", JSON.stringify(stationData))
var station = Rad.getStationFromRadioJson(response);
console.debug("Playing station:", JSON.stringify(station))
player.play(stationData);
player.play(station);
loading = false;
});
}
......@@ -38,8 +42,8 @@ Python {
function addRadIoAsFavorite(id) {
loading = true;
call('api.radio.getStationById', [''+id], function(response) {
var stationData = Rad.getStationFromRadioJson(response);
FavoritesUtils.addFavorite(stationData)
var station = Rad.getStationFromRadioJson(response);
FavoritesUtils.addFavorite(station)
loading = false
});
......@@ -48,23 +52,44 @@ Python {
function updateFavorite(oldFavorite) {
loading = true;
call('api.radio.getStationById', [''+oldFavorite.radIoId], function(response) {
var stationData = Rad.getStationFromRadioJson(response);
var station = Rad.getStationFromRadioJson(response);
FavoritesUtils.removeFavorite(oldFavorite);
FavoritesUtils.addFavorite(stationData);
FavoritesUtils.addFavorite(station);
console.log("Updated", JSON.stringify(oldFavorite));
loading = false;
});
}
function getCategories(cat, mList) {
function getGenres(mList) {
getOptionsByCategoryAndUpdateList("api.radio.getGenres", "genre", mList)
}
function getTopics(mList) {
getOptionsByCategoryAndUpdateList("api.radio.getTopics", "topic", mList)
}
function getCountries(mList) {
getOptionsByCategoryAndUpdateList("api.radio.getCountries", "country", mList)
}
function getCities(mList) {
getOptionsByCategoryAndUpdateList("api.radio.getCities", "city", mList)
}
function getLanguages(mList) {
getOptionsByCategoryAndUpdateList("api.radio.getLanguages", "language", mList)
}
function getOptionsByCategoryAndUpdateList(method, category, mList) {
window.loading = true;
call("api.radio.getCategories", [cat], function(response) {
call(method, [], function(response) {
console.log("Number of results:", response.length)
console.debug(JSON.stringify(response))
mList.clear()
response.forEach(function(title) {
mList.append({ title: title, category: cat})
response.forEach(function(response) {
mList.append({ title: response.localized, name: response.systemEnglish, category: category})
});
loading = false
......@@ -84,23 +109,44 @@ Python {
}
function getStationByCategory(category, value, mList) {
getStationsAndUpdateList("api.radio.getStationsByCategory", [category, value], mList)
console.debug("Get stations by category", category, value)
switch(category.toLocaleLowerCase()) {
case "genre":
getStationsAndUpdateList("api.radio.getStationsByGenre", [value], mList)
break;
case "topic":
getStationsAndUpdateList("api.radio.getStationsByTopic", [value], mList)
break;
case "country":
getStationsAndUpdateList("api.radio.getStationsByCountry", [value], mList)
break;
case "city":
getStationsAndUpdateList("api.radio.getStationsByCity", [value], mList)
break;
case "language":
getStationsAndUpdateList("api.radio.getStationsByLanguage", [value], mList)
break;
default:
console.error("Getting stations by unknown category", category, value)
break;
}
}
function search(s, mList) {
getStationsAndUpdateList("api.radio.getSearchResults", [s], mList)
function search(query, mList) {
getStationsAndUpdateList("api.radio.getStationsByQuery", [query], mList)
}
function getStationsAndUpdateList(method, params, mList) {
window.loading = true;
call(method, params, function(response) {
console.log("Number of results:", response.length)
console.debug(JSON.stringify(response))
mList.clear()
response.forEach(function(station) {
if(station.playable === "FREE") {
// if(station.playable === "PLAYABLE") {
mList.append(station)
}
// }
});
loading = false
......
......@@ -2,7 +2,7 @@ import QtQuick 2.6
import Sailfish.Silica 1.0
ListItem {
property string stationIcon: ""
property string stationThumbnail: ""
property string stationTitle: ""
property string stationInfo: ""
property string stationCurrent: ""
......@@ -28,7 +28,7 @@ ListItem {
Image {
id: image
source: stationIcon
source: stationThumbnail
smooth: true
fillMode: Image.PreserveAspectFit
......
......@@ -41,7 +41,7 @@ Item {
anchors.bottomMargin: hightPadding
Image {
id: stationIcon
id: stationThumbnail
height: parent.height
width: parent.height
......@@ -57,7 +57,7 @@ Item {
Column {
id: trackInfo
spacing: Theme.paddingSmall
width: parent.width - stationIcon.width
width: parent.width - stationThumbnail.width
height: parent.height
StationInfoLabel {
......
......@@ -36,7 +36,7 @@ Item {
anchors.bottomMargin: hightPadding
Image {
id: stationIcon
id: stationThumbnail
height: parent.height
width: parent.height
......@@ -52,7 +52,7 @@ Item {
StationInfoLabel {
name: window.stationData ? window.stationData.name : ""
track: currentTrack
width: parent.width - (buttonPlay.width + stationIcon.width + Theme.paddingLarge*2)
width: parent.width - (buttonPlay.width + stationThumbnail.width + Theme.paddingLarge*2)
height: parent.height
}
......
......@@ -4,49 +4,10 @@ function getStationFromRadioJson(jsonData) {
return {
radIoId: jsonData.id,
name: jsonData.name,
stationLogo: getStationLogoFromRadioJson(jsonData),
stationLogo: jsonData.thumbnail,
country: jsonData.country,
genre: jsonData.oneGenre,
url: getStationStreamFromRadioJson(jsonData)
genre: jsonData.genres,
url: jsonData.streamUrl,
description: jsonData.description
};
}
/**
* Possible data to extract
*
* "picture1Name": "t100.png",
* "picture1TransName": "t100.png",
* "picture2Name": "",
* "picture3Name": "",
* "picture4Name": "t175.png",
* "picture4TransName": "t175.png",
* "picture5Name": "",
* "picture5TransName": "",
* "picture6Name": "t44.png",
* "picture6TransName": "t44.png",
* "pictureBaseURL": "http://static.rad.io/images/broadcasts/e2/a2/10597/",
*/
function getStationLogoFromRadioJson(jsonData) {
return jsonData.pictureBaseURL+jsonData.picture1Name;
}
/**
* Get perferred stream from all streams available
*
* has no real logic now but should be extended with perferred bitrate and so on
*/
function getStationStreamFromRadioJson(jsonData) {
var streamURL = jsonData.streamURL;
for(var i = 0; i < jsonData.streamUrls.length; i++) {
var streamObj = jsonData.streamUrls[i];
if(streamObj.streamStatus === "VALID") {
streamURL = streamObj.streamUrl
}
if(streamObj.metaDataUrl) {
break;
}
};
return streamURL;
}
......@@ -2,10 +2,14 @@ import QtQuick 2.0
ListModel {
id: model
ListElement { title: qsTr("English"); apiUrl: "http://www.rad.io" }
ListElement { title: qsTr("German"); apiUrl: "http://www.radio.de" }
ListElement { title: qsTr("Austrian"); apiUrl: "http://www.radio.at" }
ListElement { title: qsTr("French"); apiUrl: "http://www.radio.fr" }
ListElement { title: qsTr("Portuguese"); apiUrl: "http://www.radio.pt" }
ListElement { title: qsTr("Spanish"); apiUrl: "http://www.radio.es" }
ListElement { title: qsTr("Austrian"); apiUrl: "http://api.radio.at" }
ListElement { title: qsTr("Danish"); apiUrl: "http://api.radio.dk" }
ListElement { title: qsTr("English"); apiUrl: "http://api.radio.net" }
ListElement { title: qsTr("French"); apiUrl: "http://api.radio.fr" }
ListElement { title: qsTr("German"); apiUrl: "http://api.radio.de" }
ListElement { title: qsTr("Italian"); apiUrl: "http://api.radio.it" }
ListElement { title: qsTr("Polish"); apiUrl: "http://api.radio.pl" }
ListElement { title: qsTr("Portuguese"); apiUrl: "http://api.radio.pt" }
ListElement { title: qsTr("Spanish"); apiUrl: "http://api.radio.es" }
ListElement { title: qsTr("Swedish"); apiUrl: "http://api.radio.se" }
}
......@@ -5,17 +5,36 @@ import requests
USER_AGENT = "XBMC not really, Recived on sailfish"
recomendedURL = "info/broadcast/editorialreccomendationsembedded"
top100URL = "info/menu/broadcastsofcategory"
mostWantedURL = "info/account/getmostwantedbroadcastlists"
searchURL = "info/index/searchembeddedbroadcast"
categoriesURL = "info/menu/valuesofcategory"
stationsByCategoriesURL = "info/menu/broadcastsofcategory"
stationByIdURL = "info/broadcast/getbroadcastembedded"
genresURL = "info/v2/search/getgenres"
topicsURL = "info/v2/search/gettopics"
countriesURL = "info/v2/search/getcountries"
citiesURL = "info/v2/search/getcities"
languagesURL = "info/v2/search/getlanguages"
recomendedStationsURL = "info/v2/search/editorstips"
topStationsURL = "info/v2/search/topstations"
localStationsURL = "info/v2/search/localstations"
stationsByGenreURL = "info/v2/search/stationsbygenre"
stationsByTopicURL = "info/v2/search/stationsbytopic"
stationsByCountryURL = "info/v2/search/stationsbycountry"
stationsByCityURL = "info/v2/search/stationsbycity"
stationsByLanguageURL = "info/v2/search/stationsbylanguage"
stationsByQueryURL = "info/v2/search/stations"
stationByIdURL = "info/v2/search/station"
SORT_TYPES = {
'popular': 'RANK',
'az': 'STATION_NAME'
}
RESULTS_PER_PAGE = 100
class Radio_API:
def __init__(self):
self.apiBaseUrl = "http://www.rad.io"
self.sortType = SORT_TYPES['popular']
pass
def setApiBaseUrl(self, apiUrl):
......@@ -23,54 +42,88 @@ class Radio_API:
pyotherside.send('log', "setting apiBaseUrl: {}".format(apiUrl))
return True
def getGenres(self):
return self.doRequest(genresURL)
def getTopStations(self):
params = {'category': '_top'}
response = self.doRequest(top100URL, params)
return response
def getTopics(self):
return self.doRequest(topicsURL)
def getCountries(self):
return self.doRequest(countriesURL)
def getCities(self, country=None):
params = {'country': country} if country else None
return self.doRequest(citiesURL, params)
def getLanguages(self):
return self.doRequest(languagesURL)
def getRecomendedStations(self):
response = self.doRequest(recomendedURL)
return response
def getLocalStations(self):
response = self.getMostWantedStations()
return response['localBroadcasts']
def getMostWantedStations(self):
# Available lists from most wanted
# topBroadcasts, recommendedBroadcasts, localBroadcasts
# Lists that could be used if impementing login
# historyBroadcasts, bookmarkedBroadcasts
params = {'sizeoflists': '100'}
return self.doRequest(mostWantedURL, params)
def getCategories(self, category):
# 'genre', 'topic', 'country', 'city', 'language'
params = {"category": "_{0}".format(category)}
pyotherside.send('log', "getCategories for: {}".format(params))
response = self.doRequest(categoriesURL, params)
return response
def getStationsByCategory(self, category, value):
params = {"category": "_{0}".format(category), "value": value}
pyotherside.send('log', "getStationsByCategory params: {0}".format(params))
response = self.doRequest(stationsByCategoriesURL, params)
return response
def getSearchResults(self, s):
pyotherside.send('log', "Searching for: {}".format(s))
params = {"q": s, "start": "0", "rows": "100",
"streamcontentformats": "aac,mp3"}
response = self.doRequest(searchURL, params)
return response
response = self.doRequest(recomendedStationsURL)
return self.__normalizeStationData(response)
def getTopStations(self, sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize and index
params = {'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(topStationsURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getLocalStations(self, sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize and index
params = {'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(localStationsURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationsByGenre(self, genre, sorttype=SORT_TYPES['popular'], sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize, index and sorttype
params = {'genre': genre, 'sorttype': sorttype, 'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(stationsByGenreURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationsByTopic(self, topic, sorttype=SORT_TYPES['popular'], sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize, index and sorttype
params = {'topic': topic, 'sorttype': sorttype, 'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(stationsByTopicURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationsByCountry(self, country, sorttype=SORT_TYPES['popular'], sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize, index and sorttype
params = {'country': country, 'sorttype': sorttype, 'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(stationsByCountryURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationsByCity(self, city, sorttype=SORT_TYPES['popular'], sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize, index and sorttype
params = {'city': city, 'sorttype': sorttype, 'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(stationsByCityURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationsByLanguage(self, language, sorttype=SORT_TYPES['popular'], sizeperpage=RESULTS_PER_PAGE, pageindex=1):
# TODO refactor to support pagesize, index and sorttype
params = {'language': language, 'sorttype': sorttype, 'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(stationsByLanguageURL, params)
# TODO error handle if categories is null
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationsByQuery(self, query, sorttype=SORT_TYPES['popular'], sizeperpage=RESULTS_PER_PAGE, pageindex=1):
pyotherside.send('log', "Searching for: {0}, with pageSize: {1} and page: {2}".format(query, sizeperpage, pageindex))
params = {'query': query, 'sorttype': sorttype, 'sizeperpage': sizeperpage, 'pageindex': pageindex}
response = self.doRequest(stationsByQueryURL, params)
return self.__normalizeStationData(response.get('categories')[0].get('matches'))
def getStationById(self, id):
pyotherside.send('log', "Getting station: {}".format(id))
params = {'broadcast': id}
params = {'station': str(id)}
response = self.doRequest(stationByIdURL, params)
return response
station = self.__addStreamUrlToStation(response)
pyotherside.send('log', "response: {}".format(station))
return self.__normalizeStationData((station, ))[0]
def doRequest(self, url, params=None):
headers = {'user-agent': USER_AGENT}
......@@ -80,4 +133,70 @@ class Radio_API:
r = requests.get(url, params=params, headers=headers)
return r.json()
@staticmethod
def __normalizeStationData(stations):
normalizedStations = []
for station in stations:
pyotherside.send('debug', "Normalizing station data for: {0}".format(station))
thumbnail = (station.get('logo300x300') or
station.get('logo175x175') or
station.get('logo100x100') or
station.get('logo44x44'))
try:
genre = [g['value'] for g in station.get('genres')]
except:
genre = [g for g in station.get('genres')]
description = Radio_API.__getStationPropertyValue(station.get('description'))
name = Radio_API.__getStationPropertyValue(station.get('name'))
country = Radio_API.__getStationPropertyValue(station.get('country'))
normalizedStation = {
'id': station['id'],
'name': name,
'thumbnail': thumbnail,
'rating': station.get('rank', ''),
'genres': ', '.join(genre),
'country': country,
'nowPlaying': station.get('nowPlaying') or '-',
'description': description,
'playable': station.get('playable'),
'streamUrl': station.get('streamUrl')
}
pyotherside.send('debug', "Normalized station data to: {0}".format(normalizedStation))
normalizedStations.append(normalizedStation)
return normalizedStations
@staticmethod
def __getStationPropertyValue(property):
''' Helper method
Some endpoints returns e.g {"name": "some name"}
and others {"name": {"matchHighlights": [], "value": "some name"}}
'''
try:
return property.get('value') if property else ''
except:
return property
@staticmethod
def __addStreamUrlToStation(station):
''' Helper method
Select a stream url that is valid.
has no real logic now but should be extended with perferred bitrate and so on
'''
for stream in station.get('streamUrls'):
if stream.get('streamStatus') == "VALID":
station['streamUrl'] = stream.get('streamUrl')
if stream.get('metaDataAvailable'):
break
return station
radio = Radio_API();
......@@ -11,7 +11,7 @@ CoverBackground {
anchors.left: parent.left
title: AudioPlayer.title
icon: window.stationData ? window.stationData.stationLogo : ""
thumbnail: window.stationData ? window.stationData.stationLogo : ""
}
......
......@@ -3,10 +3,10 @@ import Sailfish.Silica 1.0
Item {
property alias title: titleText.text
property alias icon: stationIcon.source
property alias thumbnail: stationThumbnail.source
Image {
id: stationIcon
id: stationThumbnail
opacity: 0.1
smooth: true
......
......@@ -8,7 +8,7 @@ BrowseByCategoryPageForm {
browseByListView.onCurrentIndexChanged: {
var item = browseByModel.get(browseByListView.currentIndex)
pageStack.push(Qt.resolvedUrl("StationsPage.qml"), {listType: new Utils.ListType(item.title),
category: item.category, value: item.title});
category: item.category, value: item.name});
}
......@@ -24,6 +24,25 @@ BrowseByCategoryPageForm {
}
Component.onCompleted: {
radioAPI.getCategories(category.toLocaleLowerCase(), browseByModel);
switch(category.toLocaleLowerCase()) {
case "genre":
radioAPI.getGenres(browseByModel);
break;
case "topic":
radioAPI.getTopics(browseByModel);
break;
case "country":
radioAPI.getCountries(browseByModel);
break;
case "city":
radioAPI.getCities(browseByModel);
break;
case "language":
radioAPI.getLanguages(browseByModel);
break;
default:
console.error("Browsing by unhandled category", category)
break;
}
}
}
......@@ -8,7 +8,7 @@ Dialog {
property alias url: urlField.text
property alias name: nameField.text
property alias logo: logoField.text
property alias thumbnail: thumbnailField.text
property alias country: countryField.text
property alias genre: genreField.text
......@@ -43,11 +43,11 @@ Dialog {
text: station ? station.name : ""
label: qsTr("Name")