Xiaomi Mi Internet Radio custom station(s)
disclaimer
Right now this is more like a notepad for me. Have to clear this stuff up a bit :-)
This is intended purely for private use. Software may fry your cat when flashing the micro-oven with it, or even when using the software near a micro-oven. Also might apply when your cat listens to the radio. So use at your own risk.
introduction
Basic steps to play own radio stations on the Xiaomi Mi Internet Radio:
- Recode an existing stream to a format that the Xiaomi Mi Internet Radio can play
- Pretend against the Mi Home app that you are api.ximalaya.com (e.g. hosts editor or DNS) and serve http://api.ximalaya.com/openapi-gateway-app/live/radios and/or http://api.ximalaya.com/openapi-gateway-app/search/radios with your custom stations (can be disabled once the radio knows your stations)
- Add custom stations using the Mi Home app to the radio and enjoy
prerequisites
radio hrdware
Xiaomi Global
Xiaomi WiFi Radio at AliExpress
Xiaomi WiFi Radio at gearbest
Xiaomi WiFi Radio at ebay
ffmpeg
You will need ffmpeg (optional with Fraunhofer FDK AAC support, other AAC libraries might work as well).
Since I am running CentOS 6 I added another RPM source to get a more recent ffmpeg. Details about the repository I used: https://li.nux.ro/repos.html.
This is basically what I did:
sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
sudo yum install faac.x86_64 fdk-aac.x86_64 ffmpeg ffmpeg-devel -y
webserver
At this point you can get creative. You will just need any webserver to access your own custom stations with the Mi Home app.
You will need to be able to serve something on your server like /openapi-gateway-app/live/radios and/or /openapi-gateway-app/search/radios. To get it running at all you just need a plain text (actually json) file to be served.
In addition you will need something that redirects all other requests to the original API since they are using oauth now. I did this using a .htacces like:
RewriteEngine on
RewriteRule "^openapi-gateway-app(.*)" "http://api.ximalaya.com/openapi-gateway-app$1" [P]
RewriteRule "^oauth2(.*)" "http://api.ximalaya.com/oauth2$1" [P]
And you will need to be able to server the playlist and audio chunks. In my case something like:
/radio/527782023/pl.m3u8
and
/radio/527782023/64000.aac
/radio/527782023/64001.aac
/radio/527782023/64002.aac
/radio/527782023/64003.aac
/radio/527782023/64004.aac
.
android app
You need the Xiaomi Mi home app to set up the radio. I had issues adding the radio to the current version (4.0.8 as time of writing) so I had to use an older version (3.7.15) that I got here: https://apkplz.com/android-apps/mihome/mihome-3-7-15-apk-download.
Update December 2020: Xiaomi seems to be using oauth2 now so the old app does not work for selecting stations anymore but the current one in the app-store (5.9.19) does.
To redirect the query for the radio stations you have to pretend that you are api.ximalaya.com. Either configure your own DNS to server your server's IP when asked for that hostname or edit the hostfile on your phone (requires root). I used Hosts Editor). For me it only worked when selecting Mainland China as location.
Since I was too lazy too root my phone/tablet I just installed an Android emulator for PC (MEmu) and installed bot apps there.

get the server running
stream encoder
I am running the encoder in a screen on my CentOS server. I is running always (be careful if you have traffic limits or something like that) so it is accessible "instantly".
To get all commands in one place i created a bash-script:
#!/bin/bash
FFMPEG=/usr/bin/ffmpeg
STREAMURL="http://streams.deltaradio.de/delta-foehnfrisur/aac-64/mediaplayer/"
RADIOID=527782023
# in my case i do not ne to re-encode the webstry, so copy is sufficient
ENCODE="-c:a copy"
# check available aac encoders with ffmpeg -encoders | grep -i aac
# to enable re-encoding using the fdk-aac lib mentioned above, uncomment next line
#ENCODE="-c:a libfdk_aac -b:a 64k"
#ENCODE="-c:a libfaac -b:a 64k"
OUTPUTDIR="/var/www/html/radio/${RADIOID}/"
SEGLIST="${OUTPUTDIR}/pl.m3u8"
SEGLISTPREFIX="http://[YOUR SERVER HOSTNAME OR IP]/radio/${RADIOID}/"
OUTPUT="${OUTPUTDIR}64%03d.aac"
# with a dialup connection that gets disconnected every 24h
# we need to re-run ffmpeg in case of disconnected
# - hence the while true loop
while true; do
${FFMPEG} \
-i "${STREAMURL}" \
${ENCODE} \
-f ssegment \
-segment_list ${SEGLIST} \
-segment_list_flags +live \
-segment_time 7 \
-segment_list_size 3 \
-segment_wrap 5 \
-segment_list_entry_prefix ${SEGLISTPREFIX} \
"${OUTPUT}"
done
Again: This is suitable for me running inside a screen on my server. Feel free to run it with crontab or as a service or whatever you like.
radio list
The radio list to be servered has to be accessible at /openapi-gateway-app/live/radios and/or /openapi-gateway-app/search/radios on your server. It does not have to be the same server as the encoder is running. In my example I am just serving a plain static json file (this example contains only one station on one page, you can add more if you like - and respond to searches and so on, take a look at radiosearch.php from andr68rus to get an idea):{
"total_page":1,
"total_count":1,
"current_page":0,
"radios":[
{
"id":527782023,
"kind":"radio",
"program_name":"Foehnfrisur",
"radio_name":"Deltaradio Foehnfrisur",
"radio_desc":"Feinsten Hardrock und Heavy Metal gibt es hier!",
"schedule_id":0,
"support_bitrates":[
64
],
"rate24_aac_url":"",
"rate64_aac_url":"http://[YOUR SERVER HOSTNAME OR IP]/radio/527782023/pl.m3u8",
"rate24_ts_url":"",
"rate64_ts_url":"",
"radio_play_count":1,
"cover_url_small":"http://[YOUR SERVER HOSTNAME OR IP]/radios/527782023/cover_small.png",
"cover_url_large":"http://[YOUR SERVER HOSTNAME OR IP]/radios/527782023/cover_big.png",
"updated_at":0,
"created_at":0
}
]
}
add radio station to your radio
Open the Mi Home app and search for radios. You should see your only your own station.
Step by Step
- Open up the MI Home app:

Select your device here.
- Device's dashboard:

This is basically your device's dashboard, showing in the bottom what is playing and your favorite stations. Click on the gray plus in the upper left corner to add another favorite.
- Select favorites:

To get to your own stations click on the icon in the upper right corner that looks like a radio station's antenna.
- Add your own station:

Click on the plus next to your own station to add it as a favorite.
- Own station playing:

The gray checkmark next to your station indicates that it has been added as a favorite. If you click on the station it will be played (see bottom).
- Own station as favorite:

Going back to the dashboad shows your own station 😎👍!
more than one station
Although I only wanted to encode one station the demand for more than one station arose and I made a litte script to help me with that.
It is basically a shell script (bash) that will look through a defined folder and create a encoder for every subfolder found and checks every 5 secconds if they are still running. If not it will restart the encoding.
Upon startup it will write a json file listing all the stations.
Upon CTR+C it will kill all of the started encoders.
radioencode.sh:
#!/bin/bash
#############################################################################
################################ config part ################################
#############################################################################
# I had some issues with current ffmpeg versions and the %03d part of the files
# tests with 3.1.7 were OK, 3.3. failed for me
FMPEG=/usr/bin/ffmpeg
# if copy is sufficient
#ENCODE="-c:a copy"
# check available aac encoders with ffmpeg -encoders | grep -i aac
ENCODE="-c:a libfdk_aac -b:a 64k"
#ENCODE="-c:a libfaac -b:a 64k"
WEBBASEDIR="/var/www/html/radio"
TMPDIR="/tmp/radio"
WWWPREFIX="http://[MY PUBLIC IP]/radio/"
JSONFILE=${WEBBASEDIR}/stations.json
#############################################################################
################################ /config part ###############################
#############################################################################
#clean up old stuff
if [ -d ${TMPDIR} ]; then
rm -rf ${TMPDIR}
fi
mkdir -p ${TMPDIR}
if [ ! -e ${WEBBASEDIR} ]; then
ln -s ${TMPDIR} ${WEBBASEDIR}
fi
#copy all stuff to temp dir to avoid disk wear...
SCRIPTDIR=$(dirname $(readlink -f $0))
find ${SCRIPTDIR}/* -prune -type d | while IFS= read -r d; do
cp -R ${d} ${TMPDIR}
done
RUNNING=1
# listen to CTRL+C
trap request_stop INT
request_stop() { RUNNING=0; }
# build json with stations
# should be available via /openapi-gateway-app/live/radios
STATIONCOUNT=`find ${TMPDIR} -type f -iname "stream.url" | wc -l`
cat >${JSONFILE} <<EOL
{
"total_page":1,
"total_count":${STATIONCOUNT},
"current_page":0,
"radios":[
EOL
LASTSTATION=`find ${TMPDIR}/ -type f -iname "stream.url" | tail -n 1`
find ${TMPDIR} -type f -iname "stream.url" -print0 | while IFS= read -r -d $'\0' FILE; do
OUTPUTDIR=`dirname ${FILE}`
RADIOID=`basename ${OUTPUTDIR}`
SEGLIST="${OUTPUTDIR}/pl.m3u8"
OUTPUT="${OUTPUTDIR}/64%03d.aac"
STREAMURL=`cat ${FILE}`
STATIONNAME=`cat ${OUTPUTDIR}/stationname.txt`
cat >>${JSONFILE} <<EOL
{
"id":${RADIOID},
"kind":"radio",
"program_name":"${STATIONNAME}",
"radio_name":"${STATIONNAME}",
"radio_desc":"",
"schedule_id":0,
"support_bitrates":[
64
],
"rate24_aac_url":"",
"rate64_aac_url":"${WWWPREFIX}${RADIOID}/pl.m3u8",
"rate24_ts_url":"",
"rate64_ts_url":"",
"radio_play_count":1,
"cover_url_small":"${WWWPREFIX}${RADIOID}/cover_small.png",
"cover_url_large":"${WWWPREFIX}${RADIOID}/cover_big.png",
"updated_at":0,
"created_at":0
}
EOL
if [ ${FILE} != ${LASTSTATION} ]; then
echo , >> ${JSONFILE}
fi
done
cat >>${JSONFILE} <<EOL
]
}
EOL
echo json file written to ${JSONFILE}
echo make sure it is linked to be accessible like "ln -s ${JSONFILE} /var/www/html/openapi-gateway-app/live/radios"
# clear all aac parts
find ${TMPDIR}/ -type f -iname "*.aac" | xargs rm
# with a dialup connection that gets disconnected every 24h
# we need to re-run ffmpeg in case of disconnected
# - hence the while true loop
while [[ ${RUNNING} > 0 ]]; do
find ${TMPDIR}/ -type f -iname "stream.url" -print0 | while IFS= read -r -d $'\0' FILE; do
OUTPUTDIR=`dirname ${FILE}`
RADIOID=`basename ${OUTPUTDIR}`
SEGLIST="${OUTPUTDIR}/pl.m3u8"
OUTPUT="${OUTPUTDIR}/64%03d.aac"
STREAMURL=`cat ${FILE}`
STATIONNAME=`cat ${OUTPUTDIR}/stationname.txt`
PID=`ps axwww | grep ${RADIOID} | grep -v grep | head -n 1 | awk '{print $1}'`
CURRENTTIME=$(date +%s)
LASTCHANGED=`find ${OUTPUTDIR} -type f -iname "*.aac" | xargs stat -c "%Y" | sort -r | head -n 1`
FILEAGE=$((CURRENTTIME-LASTCHANGED))
if [[ ${FILEAGE} > 7 ]]; then
echo " File too old... Killing encoder..."
kill ${PID}
sleep 1
fi
PID=`ps axwww | grep ${RADIOID} | grep -v grep | head -n 1 | awk '{print $1}'`
echo ${PID} > "${OUTPUTDIR}/ffmpeg.pid"
if [ -z "${PID}" ]; then
echo Starting ffmpeg for ${STATIONNAME} with ID ${RADIOID}
if [[ ${STREAMURL} == *".m3u" ]]; then
STREAMURL=`curl -s ${STREAMURL}`
echo "New stream url from playlist: ${STREAMURL}"
fi
${FFMPEG} \
-i "${STREAMURL}" \
${ENCODE} \
-f ssegment \
-segment_list ${SEGLIST} \
-segment_list_flags +live \
-segment_time 7 \
-segment_list_size 3 \
-segment_wrap 5 \
-segment_list_entry_prefix ${WWWPREFIX}${RADIOID}/ \
"${OUTPUT}" > /dev/null 2>&1 &
PID=`ps axwww | grep ${RADIOID} | grep -v grep | head -n 1 | awk '{print $1}'`
echo ${PID} > "${OUTPUTDIR}/ffmpeg.pid"
echo " PID ${PID}"
fi
done
sleep 5
done
echo Stopping all encoders...
find ${TMPDIR} -type f -iname "ffmpeg.pid" -print0 | while IFS= read -r -d $'\0' FILE; do
PID=`cat ${FILE}`
if [ ! -z "${PID}" ]; then
echo " Stopping ${PID}"
kill ${PID}
fi
rm ${FILE}
done
Download the script with samples here: xiaomiwifiradio-multistations-0.1.tar.bz2
F.A.Q.
What are the menu points in the app?
- Top menu:

- Timer menu:

- Startup channel menu:

thanks and related projects
|