Skip to content

Commit

Permalink
add DLNA service
Browse files Browse the repository at this point in the history
  • Loading branch information
schreibfaul1 committed Mar 24, 2023
1 parent f057260 commit d01e3b4
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,6 @@
"cinttypes": "cpp",
"typeinfo": "cpp"
},
"C_Cpp_Runner.useMsvc": false
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.useAddressSanitizer": false
}
6 changes: 2 additions & 4 deletions miniwebradio.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Name, Type, SubType, Offset, Size, Flags
phy_init, data, phy, 0x9000, 0x7000,
factory, app, factory, 0x10000, 0x300000,
nvs, data, nvs, 0x310000, 0x32000,
spiffs, data, spiffs, 0x342000, 0xB0000,
eeprom, data, 0x99, 0x3F2000, 0xD000,
factory, app, factory, 0x10000, 0x3A0000,
nvs, data, nvs, 0x3B0000, 0x40000,
8 changes: 7 additions & 1 deletion src/common.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// created: 10.Feb.2022
// updated: 14.Mar.2023
// updated: 23.Mar.2023

#pragma once
#pragma GCC optimize("Os") // optimize for code size

#define _SSID "mySSID" // Your WiFi credentials here
#define _PW "myWiFiPassword"
Expand Down Expand Up @@ -193,6 +194,11 @@ void connecttohost(const char* host);
void connecttoFS(const char* filename, uint32_t resumeFilePos = 0);
void stopSong();
void IRAM_ATTR headphoneDetect();
int DLNA_setCurrentServer(String serverName);
void DLNA_showServer();
void DLNA_browseServer(String objectId, uint8_t level);
void DLNA_getFileItems(String uri);
void DLNA_showContent(String objectId, uint8_t level);

//prototypes (audiotask.cpp)
void audioInit();
Expand Down
156 changes: 151 additions & 5 deletions src/index.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* index.h
*
* Created on: 04.10.2018
* Updated on: 05.05.2022
* Updated on: 24.03.2023
* Author: Wolle
*
* successfully tested with Chrome and Firefox
Expand Down Expand Up @@ -378,6 +378,19 @@ function connect() {
if(val == '1') document.getElementById('chk_timeSpeech').checked = true;
break

case "DLNA_Names": showServer(val)
break
case "Level1": show_DLNA_Content(val, 1)
break
case "Level2": show_DLNA_Content(val, 2)
break
case "Level3": show_DLNA_Content(val, 3)
break
case "Level4": show_DLNA_Content(val, 4)
break
case "Level5": show_DLNA_Content(val, 5)
break

default: console.log('unknown message', msg, val)
}
}
Expand Down Expand Up @@ -464,8 +477,14 @@ function showTab3 () {
document.getElementById('btn3').src = 'SD/png/MP3_Yellow.png'
document.getElementById('btn4').src = 'SD/png/Search_Green.png'
document.getElementById('btn5').src = 'SD/png/About_Green.png'
document.getElementById('level1').options.length = 0
document.getElementById('level2').options.length = 0
document.getElementById('level3').options.length = 0
document.getElementById('level4').options.length = 0
document.getElementById('level5').options.length = 0
socket.send("change_state=6")
socket.send("audiolist") // Now get the audio file list from SD
socket.send('DLNA_getServer')
}

function showTab4 () {
Expand Down Expand Up @@ -512,6 +531,103 @@ function uploadTextFile (fileName, content) {
xhr.send(fd) // send
}

// ----------------------------------- DLNA ------------------------------------
function showServer(val){
console.log(val)
var select = document.getElementById('server')
select.options.length = 0;
var server = val.split(",")
for (i = -1; i < (server.length); i++) {
opt = document.createElement('OPTION')
if(i == -1){
opt.value = ""
opt.text = "Select a DLNA Server here"
}
else{
console.log(server[i])
opt.value = server[i]
opt.text = server[i]
}
select.add(opt)
}
}
function show_DLNA_Content(val, level){
var select
if(level == 1) select = document.getElementById('level1')
if(level == 2) select = document.getElementById('level2')
if(level == 3) select = document.getElementById('level3')
if(level == 4) select = document.getElementById('level4')
if(level == 5) select = document.getElementById('level5')
content =JSON.parse(val)
//console.log(ct[1].name)
select.options.length = 0;
for (i = -1; i < (content.length); i++) {
opt = document.createElement('OPTION')
if(i == -1){
opt.value = ""
opt.text = "Select level " + level.toString()
}
else{
var n
var c
if(content[i].isDir == true){
n = content[i].name.concat('\xa0\xa0', '<DIR>'); // more than one space
c = 'D=' + content[i].id // is directory
}
else{
n = content[i].name + '\xa0\xa0' + content[i].size;
c = 'F=' + content[i].id // is file
}
opt.value = c
opt.text = n
}
select.add(opt)
}
}
function selectserver (presctrl) { // preset, select a server, root, level0
socket.send('DLNA_getContent0=' + presctrl.value)
select = document.getElementById('level1'); select.options.length = 0; // clear next level
select = document.getElementById('level2'); select.options.length = 0;
select = document.getElementById('level3'); select.options.length = 0;
select = document.getElementById('level4'); select.options.length = 0;
select = document.getElementById('level5'); select.options.length = 0;
console.log('DLNA_getContent0=' + presctrl.value)
}
function select_l1 (presctrl) { // preset, select root
socket.send('DLNA_getContent1=' + presctrl.value)
select = document.getElementById('level2'); select.options.length = 0; // clear next level
select = document.getElementById('level3'); select.options.length = 0;
select = document.getElementById('level4'); select.options.length = 0;
select = document.getElementById('level5'); select.options.length = 0;
console.log('DLNA_getContent1=' + presctrl.value)
}
function select_l2 (presctrl) { // preset, select level 1
socket.send('DLNA_getContent2=' + presctrl.value)
select = document.getElementById('level3'); select.options.length = 0;
select = document.getElementById('level4'); select.options.length = 0;
select = document.getElementById('level5'); select.options.length = 0;
console.log('DLNA_getContent2=' + presctrl.value)
}
function select_l3 (presctrl) { // preset, select level 2
socket.send('DLNA_getContent3=' + presctrl.value)
select = document.getElementById('level4'); select.options.length = 0;
select = document.getElementById('level5'); select.options.length = 0;
console.log('DLNA_getContent3=' + presctrl.value)
}
function select_l4 (presctrl) { // preset, select level 3
socket.send('DLNA_getContent4=' + presctrl.value)
select = document.getElementById('level5'); select.options.length = 0;
console.log('DLNA_getContent4=' + presctrl.value)
}
function select_l5 (presctrl) { // preset, select level 4
socket.send('DLNA_getContent5=' + presctrl.value)
console.log('DLNA_getContent5=' + presctrl.value)
}





// ----------------------------------- TAB RADIO ------------------------------------

function showLabel (id, src) { // get the bitmap from SD, convert to URL first
Expand Down Expand Up @@ -1609,9 +1725,8 @@ function getnetworks () { // tab Config: load the connected WiFi network
</center>
</div>
<!--==============================================================================================-->
<div id="tab-content3">
<div id="tab-content3">
<center>
<br>
<label for="seltrack"><big>Audio files on SD card:</big></label>
<br>
<select class="boxstyle" style="width: calc(100% -280px)"; onchange="trackreq(this)" id="seltrack">
Expand All @@ -1620,9 +1735,40 @@ function getnetworks () { // tab Config: load the connected WiFi network
<br><br>
<button class="button" onclick="socket.send('stopfile')">STOP</button>
<button class="button" onclick="socket.send('resumefile')">RESUME</button>
<br><br>
<br>
<input type="text" class="boxstyle" style="width: calc(100% - 8px);" id="resultstr3" placeholder="Waiting for a command...."> <br>
<br><br>
<br>
<hr>
<br>

<div style="flex: 0 0 calc(100% - 0px);">
<select class="boxstyle" style="width: 100%;" onchange="selectserver(this)" id="server">
<option value="-1">Select a DLNA Server here</option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l1(this)" id="level1">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l2(this)" id="level2">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l3(this)" id="level3">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l4(this)" id="level4">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l5(this)" id="level5">
<option value="-1"> </option>
</select>
</div>








</center>
</div>
<!--==============================================================================================-->
Expand Down
113 changes: 112 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
MiniWebRadio -- Webradio receiver for ESP32
first release on 03/2017
Version 2.5, Mar 14/2022
Version 2.6, Mar 23/2022
2.8" color display (320x240px) with controller ILI9341 or HX8347D (SPI) or
3.5" color display (480x320px) wiht controller ILI9486 or ILI9488 (SPI)
Expand Down Expand Up @@ -84,6 +84,12 @@ String _stationName_air = "";
String _homepage = "";
String _filename = "";

uint numServers = 0;
int currentServer = -1;
uint32_t media_downloadPort = 0;
String media_downloadIP = "";
vector<String> names{};

char _hl_item[10][40]{ // Title in headline
"** Internet Radio **", // "* интернет-радио *" "ραδιόφωνο Internet"
"** Internet Radio **",
Expand Down Expand Up @@ -114,6 +120,9 @@ TP tp(TP_CS, TP_IRQ);
File audioFile;
File playlistFile;
FtpServer ftpSrv;
WiFiClient client;
WiFiUDP udp;
SoapESP32 soap(&client, &udp);
#if DECODER == 2 // ac101
AC101 dac;
#endif
Expand Down Expand Up @@ -1176,6 +1185,8 @@ void setup(){
if(DECODER == 0) setTone(); // HW Decoder
else setI2STone(); // SW Decoder
showFooter();
soap.seekServer();
numServers = soap.getServerCount();
}
/***********************************************************************************************************************
* C O M M O N *
Expand Down Expand Up @@ -1715,6 +1726,98 @@ void changeState(int state){
_state = state;
}
/***********************************************************************************************************************
* D L N A *
***********************************************************************************************************************/
int DLNA_setCurrentServer(String serverName){
int serverNum = -1;
for(int i = 0; i < names.size(); i++){
if(names[i] == serverName) serverNum = i;
}
currentServer = serverNum;
return serverNum;
}
void DLNA_showServer(){ // Show connection details of all discovered, usable media servers
String msg = "DLNA_Names=";
soapServer_t srv;
names.clear();
for(int i = 0; i < numServers; i++){
soap.getServerInfo(i, &srv);
Serial.printf("Server[%d]: IP address: %s port: %d name: %s -> controlURL: %s\n",
i, srv.ip.toString().c_str(), srv.port, srv.friendlyName.c_str(), srv.controlURL.c_str());
msg += srv.friendlyName;
if(i < numServers - 1) msg += ',';
names.push_back(srv.friendlyName);
}
log_i("msg %s", msg.c_str());
webSrv.send(msg);
}
void DLNA_browseServer(String objectId, uint8_t level){
JSONVar myObject;
soapObjectVect_t browseResult;
soapObject_t object;

// Here the user selects the DLNA server whose content he wants to see, level 0 is root
if(level == 0){
if(DLNA_setCurrentServer(objectId) < 0) {log_e("DLNA Server not found"); return;}
objectId = "0";
}

soap.browseServer(currentServer, objectId.c_str(), &browseResult);
if(browseResult.size() == 0){
log_i("no content!"); // then the directory is empty
return;
}
log_v("objectID: %s", objectId.c_str());
for (int i = 0; i < browseResult.size(); i++){
object = browseResult[i];
myObject[i]["name"]= object.name;
myObject[i]["isDir"] = object.isDirectory;
if(object.isDirectory){
myObject[i]["id"] = object.id;
}
else {
myObject[i]["id"] = object.uri;
media_downloadPort = object.downloadPort;
media_downloadIP = object.downloadIp.toString();
}
myObject[i]["size"] = (uint32_t)object.size;
myObject[i]["uri"] = object.id;
log_v("objectName %s", browseResult[i].name.c_str());
log_v("objectId %s", browseResult[i].artist.c_str());
}
level++;
String msg = "Level" + String(level,10) + "=" + JSON.stringify(myObject);

log_v("msg = %s", msg.c_str());
webSrv.send(msg);
browseResult.clear();
}

void DLNA_getFileItems(String uri){
soapObjectVect_t browseResult;

log_v("uri: %s", uri.c_str());
log_v("downloadIP: %s", media_downloadIP.c_str());
log_v("downloadport: %d", media_downloadPort);
String URL = "http://" + media_downloadIP + ":" + media_downloadPort + "/" + uri;
log_i("URL=%s", URL.c_str());
audioConnecttohost(URL.c_str());
}
void DLNA_showContent(String objectId, uint8_t level){
log_v("obkId=%s", objectId.c_str());
if(level == 0){
DLNA_browseServer(objectId, level);
}
if(objectId.startsWith("D=")) {
objectId = objectId.substring(2);
DLNA_browseServer(objectId, level);
}
if(objectId.startsWith("F=")) {
objectId = objectId.substring(2);
DLNA_getFileItems(objectId);
}
}
/***********************************************************************************************************************
* L O O P *
***********************************************************************************************************************/
void loop() {
Expand Down Expand Up @@ -2397,6 +2500,14 @@ void WEBSRV_onCommand(const String cmd, const String param, const String arg){
if( param == "false") _f_timeAnnouncement = false;
pref.putBool("timeAnnouncing", _f_timeAnnouncement); return;}

if(cmd == "DLNA_getServer") {DLNA_showServer(); return;}
if(cmd == "DLNA_getContent0") {DLNA_showContent(param, 0); return;}
if(cmd == "DLNA_getContent1") {DLNA_showContent(param, 1); return;} // search for level 1 content
if(cmd == "DLNA_getContent2") {DLNA_showContent(param, 2); return;} // search for level 2 content
if(cmd == "DLNA_getContent3") {DLNA_showContent(param, 3); return;} // search for level 3 content
if(cmd == "DLNA_getContent4") {DLNA_showContent(param, 4); return;} // search for level 4 content
if(cmd == "DLNA_getContent5") {DLNA_showContent(param, 5); return;} // search for level 5 content

if(cmd == "test"){ sprintf(_chbuf, "free heap: %u, Inbuff filled: %u, Inbuff free: %u",
ESP.getFreeHeap(), audioInbuffFilled(), audioInbuffFree());
webSrv.reply(_chbuf);
Expand Down

0 comments on commit d01e3b4

Please sign in to comment.