MyLifeSucks.de Logo News | Fireblade Repsol | Webcam Kiel | BlinkenCUBE | Open Source MAX7456 OSD Software | HiFi & Heimkino | Ehemalige Hebbelschüler ABI 2003 | Über mich/Impressum

oss/xiaomiwifiradio


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:
  1. Recode an existing stream to a format that the Xiaomi Mi Internet Radio can play
  2. 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)
  3. 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.
Host editor on MEmu

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

  1. Open up the MI Home app:
    HowTo add custom stations to Xiaomi Mi Internet Radio step 1
    Select your device here.
  2. Device's dashboard:
    HowTo add custom stations to Xiaomi Mi Internet Radio step 2
    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.
  3. Select favorites:
    HowTo add custom stations to Xiaomi Mi Internet Radio step 3
    To get to your own stations click on the icon in the upper right corner that looks like a radio station's antenna.
  4. Add your own station:
    HowTo add custom stations to Xiaomi Mi Internet Radio step 4
    Click on the plus next to your own station to add it as a favorite.
  5. Own station playing:
    HowTo add custom stations to Xiaomi Mi Internet Radio step 5
    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).
  6. Own station as favorite:
    HowTo add custom stations to Xiaomi Mi Internet Radio step 6
    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:
    Top menu of the Xiaomi Mi Internet Radio translation
  • Timer menu:
    Timer menu of the Xiaomi Mi Internet Radio translation
  • Startup channel menu:
    Startup channel menu of the Xiaomi Mi Internet Radio translation

thanks and related projects

Valid XHTML 1.0! Valid CSS