I have a similar project using Arduino here https://www.hackster.io/khanhhs/ardu...-player-9bc616 .

MP3 player may be useful in museum or exhibition to provide voice-based information to visitors on demand. This project shows how to control MP3 via webpage using PHPoC.



Demonstration





Things Used In This Project
  • PHPoC Blue or Black
  • Serial MP3 Player
  • Speaker

Serial MP3 Player

Click image for larger version  Name:	arduino_mp3_mp3_player.PNG Views:	1 Size:	157.1 KB ID:	712

Serial MP3 player have two interfaces:
  • jack to speaker
  • interface to micro-controller (in this project is PHPoC)

When receiving a command from micro-controller (e.g PLAY, PAUSE, VOLUME UP...), MP3 player read .mp3 file from SD card and perform action based on the command.

Before using, it need to copy .mp3 files to SD card and mount it to MP3 Player.



Wiring Diagram

Click image for larger version

Name:	phpoc_mp3_wiring.PNG
Views:	90
Size:	93.0 KB
ID:	716

Connect pin GND, VCC TX and RX of MP3 Player to pin GND, PWR5, U0RX and pin U0TX of PHPoC, respectively.



Data Flow

Web browser ---> PHPoC

Web app on web browser will send commands and data based on touch or click event to PHPoC via WebSocket. When receiving the command, PHPoC sends command to MP3 player according to the command received from web browser.

Command Set

Click image for larger version

Name:	phpoc_mp3command_set.png
Views:	36
Size:	21.1 KB
ID:	717

Where, XX is volume value.



Source Code

Source code includes two files:

- index.php: This file is client side code. When receiving HTTP request from web browser, PHPoC interprets PHP script in this file, and then send the interpreted file to web browser. The interpreted file (contains html, css and JavaScript code) provides UI (User Interface), handling touch/click event from User and send command back to PHPoC via websocket.

PHP Code:
<!DOCTYPE html>
<html>
<head>
<title>PHPoC - IoT MP3 Player</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<style>
body { text-align: center; font-size: 15pt; }
h1 { font-weight: bold; font-size: 25pt; }
h2 { font-weight: bold; font-size: 15pt; }
button { font-weight: bold; font-size: 15pt; }
</style>
<script>
var canvas_width = 450, canvas_height = 250;
var trans_x = canvas_width/2, trans_y = canvas_height/2 - 30;
var plate_width = 300, plate_height = 80;
var volume_width = 300, volume_height = 8;
var circle_radius = 80;
var volume_y = circle_radius + 35;
var inner_radius;
var pause_width, pause_height;
var play_width, play_height;
var next_width, next_height, next_top, next_right;
var arrow_width;

var play_state = 0; // 0: pause. 1: playing
var volume = 20;
var click_state = 0; // 0: no click, 1: back button click, 2: next button click
var ws;

function init()
{
    var canvas = document.getElementById("remote");
    canvas.style.backgroundColor = "#999999";
    canvas.width = canvas_width;
    canvas.height = canvas_height;

    canvas.addEventListener("touchstart", mouse_down);
    canvas.addEventListener("touchend", mouse_up);
    canvas.addEventListener("touchmove", mouse_move);
    canvas.addEventListener("mousedown", mouse_down);
    canvas.addEventListener("mouseup", mouse_up);
    canvas.addEventListener("mousemove", mouse_move);

    var ctx = canvas.getContext("2d");

    ctx.translate(trans_x, trans_y);

    inner_radius = circle_radius - 25;
    next_height = Math.round(0.6 * plate_height);
    arrow_width = Math.round(next_height * Math.cos(Math.PI/3.5));
    next_width = 2*arrow_width - 7;
    next_top = next_height / 2;
    next_right = plate_width / 2 + 15;

    update_view();
}
function connect_onclick()
{
    if(ws == null)
    {
        var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>";
        if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "["))
        {
            // network resource identifier to UNC path name conversion
            ws_host_addr = ws_host_addr.replace(/[\[\]]/g, '');
            ws_host_addr = ws_host_addr.replace(/:/g, "-");
            ws_host_addr += ".ipv6-literal.net";
        }

        ws = new WebSocket("ws://" + ws_host_addr + "/remote_slide", "text.phpoc");
        document.getElementById("ws_state").innerHTML = "CONNECTING";
        ws.onopen = ws_onopen;
        ws.onclose = ws_onclose;
        ws.onmessage = ws_onmessage;
    }
    else
        ws.close();
}
function ws_onopen()
{
    document.getElementById("ws_state").innerHTML = "<font color='blue'>CONNECTED</font>";
    document.getElementById("bt_connect").innerHTML = "Disconnect";
}
function ws_onclose()
{
    document.getElementById("ws_state").innerHTML = "<font color='gray'>CLOSED</font>";
    document.getElementById("bt_connect").innerHTML = "Connect";
    ws.onopen = null;
    ws.onclose = null;
    ws.onmessage = null;
    ws = null;
}
function ws_onmessage(e_msg)
{
    e_msg = e_msg || window.event; // MessageEvent
    alert("msg : " + e_msg.data);
}
function update_view()
{
    var canvas = document.getElementById("remote");
    var ctx = canvas.getContext("2d");

    ctx.clearRect(-trans_x, -trans_y, canvas_width, canvas_height);

    ctx.fillStyle="#404040";
    ctx.beginPath();
    ctx.arc(-plate_width / 2, 0, plate_height / 2, 0.5 * Math.PI, 1.5 * Math.PI);
    ctx.lineTo(plate_width / 2, -plate_height / 2);
    ctx.arc(plate_width / 2, 0, plate_height / 2, 1.5 * Math.PI, 0.5 * Math.PI);
    ctx.lineTo(-plate_width / 2, plate_height / 2);
    ctx.fill();

    var gradient=ctx.createLinearGradient(0,-circle_radius,0,circle_radius);
    gradient.addColorStop(0,"white");
    gradient.addColorStop(0.5,"#cceeff");
    gradient.addColorStop(1,"white");

    ctx.fillStyle=gradient;
    ctx.beginPath();
    ctx.arc(0, 0, circle_radius, 0 , 2 * Math.PI);
    ctx.fill();

    var arrow1_right = next_right;
    var arrow1_left = next_right - arrow_width;
    var arrow2_left = next_right - next_width;
    var arrow2_right = arrow2_left + arrow_width;

    // Back button
    if(click_state == 1)
        ctx.fillStyle="#66ffff";
    else
        ctx.fillStyle=gradient;
    ctx.beginPath();
    ctx.lineTo(-arrow1_right, 0);
    ctx.lineTo(-arrow1_left, next_height / 2);
    ctx.lineTo(-arrow1_left, -next_height / 2);
    ctx.fill();
    ctx.beginPath();
    ctx.lineTo(-arrow2_right, 0);
    ctx.lineTo(-arrow2_left, next_height / 2);
    ctx.lineTo(-arrow2_left, -next_height / 2);
    ctx.fill();

    // Next button
    if(click_state == 2)
        ctx.fillStyle="#66ffff";
    else
        ctx.fillStyle=gradient;
    ctx.beginPath();
    ctx.lineTo(arrow1_right, 0);
    ctx.lineTo(arrow1_left, next_height / 2);
    ctx.lineTo(arrow1_left, -next_height / 2);
    ctx.fill();
    ctx.beginPath();
    ctx.lineTo(arrow2_right, 0);
    ctx.lineTo(arrow2_left, next_height / 2);
    ctx.lineTo(arrow2_left, -next_height / 2);
    ctx.fill();

    var x = Math.round(inner_radius * Math.cos(Math.PI/3));
    var y = Math.round(inner_radius * Math.cos(Math.PI/6));
    ctx.fillStyle="#2eb82e";

    if(!play_state)
    {
        // Pausing button
        ctx.beginPath();
        ctx.lineTo(inner_radius, 0);
        ctx.lineTo(-x, y);
        ctx.lineTo(-x, -y);
        ctx.fill();
    }
    else
    {
        // Playing button
        x -= 3;
        y -= 3;
        var bar_width = 14;
        ctx.beginPath();
        ctx.lineTo(-x - bar_width, -y);
        ctx.lineTo(-x - bar_width, y);
        ctx.lineTo(-x + bar_width, y);
        ctx.lineTo(-x + bar_width, -y);
        ctx.fill();
        ctx.beginPath();
        ctx.lineTo(x - bar_width, -y);
        ctx.lineTo(x - bar_width, y);
        ctx.lineTo(x + bar_width, y);
        ctx.lineTo(x + bar_width, -y);
        ctx.fill();
    }

    // Volume
    volume_height += 4;
    ctx.fillStyle="#404040";
    ctx.beginPath();
    ctx.arc(-volume_width / 2, volume_y, volume_height / 2, 0.5 * Math.PI, 1.5 * Math.PI);
    ctx.lineTo(volume_width / 2, volume_y - volume_height / 2);
    ctx.arc(volume_width / 2, volume_y, volume_height / 2, 1.5 * Math.PI, 0.5 * Math.PI);
    ctx.lineTo(-volume_width / 2, volume_y + volume_height / 2);
    ctx.fill();

    volume_height -= 6;
    var temp = volume * volume_width / 30 - volume_width / 2;
    ctx.fillStyle="#2eb82e";
    ctx.beginPath();
    ctx.arc(-volume_width / 2, volume_y, volume_height / 2, 0.5 * Math.PI, 1.5 * Math.PI);
    ctx.lineTo(temp, volume_y - volume_height / 2);
    ctx.arc(temp, volume_y, volume_height / 2, 1.5 * Math.PI, 0.5 * Math.PI);
    ctx.lineTo(-volume_width / 2, volume_y + volume_height / 2);
    ctx.fill();
    volume_height += 2;
    ctx.fillStyle="white";
    ctx.beginPath();
    ctx.arc(temp, volume_y, volume_height*2, 0, 2 * Math.PI);
    ctx.fill();

}
function mouse_down()
{
    if(ws == null)
        return;

    event.preventDefault();

    var x, y;

    if(event.offsetX)
    {
        x = event.offsetX - trans_x;
        y = event.offsetY - trans_y;
    }
    else if(event.layerX)
    {
        x = event.layerX - trans_x;
        y = event.layerY - trans_y;
    }
    else
    {
        x = (Math.round(event.touches[0].pageX - event.touches[0].target.offsetLeft)) - trans_x;
        y = (Math.round(event.touches[0].pageY - event.touches[0].target.offsetTop)) - trans_y;
    }

    var radius = Math.sqrt(x*x + y*y);
    if(radius < inner_radius)
    {
        // pause/play button is click
        play_state = (play_state + 1)%2;

        if(play_state)
            ws.send("P" + "0\r\n");
        else
            ws.send("S" + "0\r\n");
    }
    else if((y < next_top) && (y > -next_top))
    {
        if((x < next_right) && (x > (next_right-next_width)))
        {
            ws.send("N" + "0\r\n");
            console.log("next button is pressed!");
            click_state = 2;
        }
        else if((x > -next_right) && (x < -(next_right-next_width)))
        {
            ws.send("B" + "0\r\n");
            console.log("back button is pressed!");
            click_state = 1;
        }
    }
    else
    {
        if((y > (volume_y - volume_height*3))
            && (y < (volume_y + volume_height*3))
            && (x < (volume_width / 2))
            && (x > -(volume_width / 2)))
            {
                volume = Math.round((volume_width / 2 + x)/ volume_width *30);
                ws.send("V" + volume.toString() + "\r\n");
                console.log("volume changed!");
            }
    }

    update_view();
}
function mouse_up()
{
    if(ws == null)
        return;

    event.preventDefault();

    click_state = 0;

    update_view();
}
function mouse_move()
{
    if(ws == null)
        return;

    event.preventDefault();

    var x, y;

    if(event.offsetX)
    {
        x = event.offsetX - trans_x;
        y = event.offsetY - trans_y;
    }
    else if(event.layerX)
    {
        x = event.layerX - trans_x;
        y = event.layerY - trans_y;
    }
    else
    {
        x = (Math.round(event.touches[0].pageX - event.touches[0].target.offsetLeft)) - trans_x;
        y = (Math.round(event.touches[0].pageY - event.touches[0].target.offsetTop)) - trans_y;
    }

    if((y > (volume_y - volume_height*3))
    && (y < (volume_y + volume_height*3))
    && (x < (volume_width / 2))
    && (x > -(volume_width / 2)))
    {
        volume = Math.round((volume_width / 2 + x)/ volume_width *30);
        ws.send("V" + volume.toString() + "\r\n");
        console.log("volume changed!");
    }

    update_view();
}
window.onload = init;
</script>
</head>

<body>

<p>
<h1>Web Remote Control / IoT MP3 Player</h1>
</p>

<canvas id="remote"></canvas>

<h2>
<p>
WebSocket : <span id="ws_state">null</span>
</p>
<button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button>
</h2>

</body>
</html>



- task0.php: This file is server side code. It is run in infinite loop to receive and handle command from web browser, control MP3 player.

PHP Code:
<?php

if(_SERVER("REQUEST_METHOD"))
    exit; 
// avoid php execution via http request

include "/lib/sd_340.php";
include 
"/lib/sn_tcp_ws.php";

define("CMD_PLAY_NEXT",        0x01);
define("CMD_PLAY_PREV",        0x02);
define("CMD_PLAY_W_INDEX",    0x03);
define("CMD_SET_VOLUME",    0x06);
define("CMD_SEL_DEV",        0x09);
define("CMD_PLAY_W_VOL",    0x22);
define("CMD_PLAY",            0x0D);
define("CMD_PAUSE",            0x0E);
define("CMD_SINGLE_CYCLE",    0x19);
define("DEV_TF",            0x02);
define("SINGLE_CYCLE_ON",    0x00);
define("SINGLE_CYCLE_OFF",    0x01);

function 
mp3_send_cmd($cmd$data)
{
    
$cmd_buf  "\x7E"/* $S - Every command should start with $(0x7E) */
    
$cmd_buf .= "\xFF"/* VER - Version information */
    
$cmd_buf .= "\x06"/* Len - The number of bytes of the command without starting byte and ending byte */
    
$cmd_buf .= int2bin($cmd1);   /* CMD - Such as PLAY and PAUSE and so on */
    
$cmd_buf .= "\x00"/* Feedback - 0x00 = not feedback, 0x01 = feedback */
    
$cmd_buf .= int2bin($data2TRUE);  /* data - usually it has two bytes */
    
$cmd_buf .= "\xEF"/* $O - Ending byte of the command */

    
uart_write(0$cmd_buf8);
}

uart_setup(09600"N81N");
ws_setup(0"remote_slide""text.phpoc");

$cmd_buf "";
$rbuf "";

while(
1)
{
    if(
ws_state(0) == TCP_CONNECTED)
    {
        
$rlen ws_read_line(0$rbuf);

        if(
$rlen)
        {
            
$name $rbuf[0];
            
$value = (int)substr($rbuf1);

            if(
$name == 'P')
            {
                 echo 
"Play mp3\r\n";
                 
mp3_send_cmd(CMD_PLAY0x0000);
            }

            if(
$name == 'S')
            {
                 echo 
"Pause mp3\r\n";
                 
mp3_send_cmd(CMD_PAUSE0x0000);
            }

            if(
$name == 'N')
            {
                 echo 
"Play next mp3\r\n";
                 
mp3_send_cmd(CMD_PLAY_NEXT0x0000);
            }
            if(
$name == 'B')
            {
                 echo 
"Play previous mp3\r\n";
                 
mp3_send_cmd(CMD_PLAY_PREV0x0000);
            }
            if(
$name == 'V')
            {
                 echo 
"Change volume to $value\r\n";
                 
mp3_send_cmd(CMD_SET_VOLUME$value);
            }
        }
    }
}

?>