目的:ホームネットワーク上の任意のデバイスからの列の音楽の再生を制御します。
実装は次のようになります。音楽情報はデータベースに格納され、この情報をオンデマンドでプルするサーブレットがあります。
すべてはWebインターフェースを介して制御されます。
最後に、スピーカーが接続されているAndroidデバイスで、オーディオサーバーが回転しています。
それでは、行きましょう。
1.データベースの準備
データベースとしてMySQLを使用します。 データベースには2つのテーブルが含まれています。mp3-オーディオファイルのデータとmp3_tmp-データベースの更新時にテーブルが使用されます。 両方のテーブルの構造は同じです。
テーブルには次のフィールドが含まれます。
path-ディスク上のファイルへのパス、 PRIMARY KEY ;
アーティスト -パフォーマー;
album-アルバム名;
title-トラックのタイトル。
年 -記録の年。
番号 -アルバムのトラック番号。
length -mm:ss形式のトラックの長さ。
したがって、テーブルを作成するSQL:
DROP TABLE IF EXISTS `mp3_tmp`; CREATE TABLE `mp3_tmp` ( `path` varchar(250) NOT NULL, `artist` varchar(250) DEFAULT NULL, `album` varchar(250) DEFAULT NULL, `title` varchar(250) DEFAULT NULL, `year` varchar(40) DEFAULT NULL, `length` varchar(40) DEFAULT NULL, `number` varchar(40) DEFAULT NULL, PRIMARY KEY (`path`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
テーブルを作成するには、Javaのプログラムを使用します。
開始するには、音楽を含むフォルダー内のすべてのmp3ファイルのリストを作成します。
FileFinder ff = new FileFinder(); // , List<File> files = ff.findFiles(initPath, ".*\\.mp3"); // mp3- initPath
次に、一時テーブルにファイル情報を入力します
( JDBCを使用-データベースに接続し、
jaudiotaggerライブラリ-mp3タグのスキャン用):
for (File file : files) { // : PreparedStatement preparedStatement = connect.prepareStatement("insert into `mp3_tmp` " + "(path, artist, album, title, year, number, length) " + "values (?, ?, ?, ?, ?, ?, ?)"); String fullName = file.getCanonicalPath(); String fileName = fullName.replace(initPath,""); // String length = ""; try { AudioFile af = AudioFileIO.read(file); int len = af.getAudioHeader().getTrackLength(); // int sec = len%60; int min = (len-sec)/60; length = String.format("%02d:%02d", min, sec); // MP3File mp3f = new MP3File(file); Tag tag = mp3f.getTag(); // String artist = tag.getFirst(FieldKey.ARTIST); String album = tag.getFirst(FieldKey.ALBUM); String title = tag.getFirst(FieldKey.TITLE); String year = tag.getFirst(FieldKey.YEAR); String number = tag.getFirst(FieldKey.TRACK); // ( ): if (!number.equals("")){ Integer num = Integer.parseInt(number); if (num < 10) { number = "00"+num.toString(); } else if (num < 100) { number = "0"+num.toString(); } } // : preparedStatement.setString(1, fileName); preparedStatement.setString(2, artist); preparedStatement.setString(3, album); preparedStatement.setString(4, title); preparedStatement.setString(5, year); preparedStatement.setString(6, number); } catch (Exception e) { // , : preparedStatement.setString(1, fileName); preparedStatement.setString(2, ""); preparedStatement.setString(3, ""); preparedStatement.setString(4, ""); preparedStatement.setString(5, ""); preparedStatement.setString(6, ""); } finally { preparedStatement.setString(7, length); preparedStatement.executeUpdate(); // } } //, , : statement.execute("DROP TABLE IF EXISTS `mp3`"); statement.execute("CREATE TABLE `mp3` LIKE `mp3_tmp`"); statement.execute("INSERT INTO `mp3` SELECT * FROM `mp3_tmp`");
2.バックエンドプレーヤー
プレーヤーのバックエンドは、2つのサーブレットで構成されています。
@WebServlet( "/ getlist")-検索クエリのトラックのリストを返します。
@WebServlet( "/ヒント")-ツールチップを最初の文字で返します。
GetListサーブレットの詳細。
// SQL- String query = "select path, artist, title, album, year, length from `mp3` where "; String[] qArr0 = request.getParameter("query").split("\\|"); for (int k=0; k<qArr0.length; k++) { if (k>0) query += " or "; String[] qArr = qArr0[k].split(" "); query += "concat(title,' ',album,' ',artist) like "+"'%"+qArr[0]+"%' "; for (int j=1; j<qArr.length; j++) { query += "and concat(title,' ',album,' ',artist) like "+"'%"+qArr[j]+"%' "; } } query += "group by concat(year,album,number,title,artist,length) "; query += "order by concat(year,' ',album,' ',number,' ',title,' ',artist)"; Statement statement = connect.createStatement(); resultSet = statement.executeQuery(query); // // GSON json Gson gson = new GsonBuilder().create(); int i=0; while (resultSet.next()) { if (i++>0) playlist+="\n"; String artist = resultSet.getString("artist"); String title = resultSet.getString("title"); String album = resultSet.getString("album"); String year = resultSet.getString("year"); String path = resultSet.getString("path"); path = musicPath+path; String length = resultSet.getString("length"); Track track = new Track(title, artist, album, year, path, length); playlist += gson.toJson(track); }
ヒントサーブレットは、次のSQLクエリを実行します。
// String text = request.getParameter("query"); Statement statement = connect.createStatement(); resultSet = statement.executeQuery("select title as 'str' from " + "`mp3` where title like '%" + text + "%' union " + "select artist as 'str' from " + "`mp3` where artist like '%" + text + "%' union " + "select album as 'str' from " + "`mp3` where album like '%" + text + "%' group by str order by str limit 10");
JSON形式でデータを返します。
3. HTML5フロントエンド
プレーヤーのフロントエンドとしてHTML5とjQueryを使用します 。 ここで興味深い点は次のとおりです。
プレイリストの形成:
$('#search').click(function(){ // - var uri = '/mp3player/getlist?query='+$('#query').val(); $.get(encodeURI(uri),function(data){ window.playlist = []; window.number = 0; var array = data.split('\n'); for (i=0; i<array.length; i++) { window.playlist[i] = $.parseJSON(array[i]); } // HTML : var li_list = '<ol>'; var album = ''; var year = ''; var alb_num = 0; for (i=0; i<window.playlist.length; i++) { if (album != window.playlist[i].album || i == 0) { album = window.playlist[i].album; year = window.playlist[i].year; var str = ' '; if (album) str = album; if (year) str+= ' | ' + year; li_list += '<h4 class="album" alb_num='+alb_num+'>'+str+'</h4>'; alb_num++; } li_list += '<li class="track" id='+i+' alb_num='+alb_num+'>' +window.playlist[i].title+' ('+window.playlist[i].artist+') | '+window.playlist[i].length+'</li>'; } li_list += '</ol>'; // : $('#list').html(li_list); if (window.playlist.length>1) { window.alb_num = $('li#'+window.number).attr('alb_num'); $('#list').scrollTop($('li#'+window.number).offset().top-$('li#0').offset().top); } // : $('li.track').click(function(){ $('#toogle').prop('disabled',false); $('#toogle').prop('checked',true); $('li#'+window.number).attr('style','font-weight:normal; font-style:normal'); window.number = $(this).attr('id'); if (window.playlist.length>1) { window.alb_num = $('li#'+window.number).attr('alb_num'); $('#list').animate({scrollTop:($('li#'+window.number).offset().top-$('li#0').offset().top)},200); } $('li#'+window.number).attr('style','font-weight:bold; font-style:italic'); $('#my_audio').trigger('pause'); // #my_audio - html5 audio. . var track = window.playlist[window.number]; $('#title').html('<h3>'+(Number(window.number)+1)+': '+track.title+' ('+track.artist+')</h3>'); $('#my_audio').attr('src',track.mp3); // $('#my_audio').trigger('play'); // }); }); });
ヒント:
// jquery.autocomplete $('#query').autocomplete({serviceUrl:'/mp3player/hint'});
ボリュームの変更:
$('#volume_slider').slider({orientation:'vertical',range:'min',min:0,max:100,value:100,stop:function(event,ui){ var volume = (ui.value*1.0)/100.0; $('#my_audio').prop('volume',volume); } });
トラックアドバンス:
$('#time_slider').slider({disabled:true,range:'min',min:0,max:1000,stop:function(event, ui) { var dur = $('#my_audio').prop('duration'); var cur = (dur*ui.value)/1000; $('#my_audio').prop('currentTime',cur); } });
現在位置のインタラクティブな表示:
$('#my_audio').bind('timeupdate',function(){ var cur = $('#my_audio').prop('currentTime'); var dur = $('#my_audio').prop('duration'); var left = dur - cur; if (dur) { var slider_val = cur*1000/dur; cur = Math.floor(cur+0.5); dur = Math.floor(dur+0.5); left = Math.floor(left+0.5); cur_s = cur % 60; cur_m = (cur - cur_s)/60; dur_s = dur % 60; dur_m = (dur - dur_s)/60; left_s = left % 60; left_m = (left - left_s)/60; cur_s = $.formatNumber(cur_s,{format:'00',locale:'ru'}); cur_m = $.formatNumber(cur_m,{format:'00',locale:'ru'}); dur_s = $.formatNumber(dur_s,{format:'00',locale:'ru'}); dur_m = $.formatNumber(dur_m,{format:'00',locale:'ru'}); left_s = $.formatNumber(left_s,{format:'00',locale:'ru'}); left_m = $.formatNumber(left_m,{format:'00',locale:'ru'}); $('#time_cur').text(cur_m+':'+cur_s+' ') $('#time_dur').text(' '+left_m+':'+left_s); $('#time_slider').slider('option',{disabled:false}); $('#time_slider').slider('value',slider_val); } });
最後に次のトラックに切り替えます。
$('#my_audio').on('ended',function(){ var n = (Number(window.number) + 1) % window.playlist.length; $('li#'+n).trigger('click'); });
完成したプレーヤーは次のとおりです。
http://home.tabatsky.ru/mp3player/homeaudio/desktop.jsp
4.オーディオサーバー
偶然、私のスピーカーは、ほこりや良いスピーカー、Android上の1つのデバイスを集めていました。 その結果、Android用のオーディオサーバーを作成し、スピーカーから音楽を聴くというアイデアがありました。
オーディオサーバーは、1つのアクティビティと2つのサービス( HttpServiceとPlayerService)で構成されます。
そして、より詳細に。
HttpServiceはHTTPリクエストを受け入れ、PlayerServiceコマンドを送信します。
public int onStartCommand(Intent intent, int flags, int startId) { t = new Thread() { public void run() { try { ss = new ServerSocket(port, backlog, InetAddress.getByName(addr)); while (true) { // Socket s = ss.accept(); Thread tt = new Thread(new SocketProcessor(s)); tt.start(); tt.join(50); } } catch (Throwable e) { e.printStackTrace(); } } }; t.start(); // BroadcastReceiver c PlayerService filter = new IntentFilter("Http"); receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String res = intent.getStringExtra("result"); if (res!=null) result = "'"+res+"'"; String stat = intent.getStringExtra("status"); if (stat!=null) status = "'"+stat+"'"; String cur = intent.getStringExtra("currentTime"); if (cur!=null) currentTime = cur; String dur = intent.getStringExtra("duration"); if (dur!=null) duration = dur; } }; registerReceiver(receiver, filter); return START_STICKY; } // HTTP- private class SocketProcessor implements Runnable { private Socket s; private InputStream is; private OutputStream os; private SocketProcessor(Socket s) throws Throwable { this.s = s; this.is = s.getInputStream(); this.os = s.getOutputStream(); } public void run() { try { // HTTP-: readInputHeaders(); // jsonp: String response = ""; response += "window.result="+result+"; "; response += "window.status="+status+"; "; response += "window.currentTime="+currentTime+"; "; response += "window.duration="+duration+"; "; writeResponse(response); } catch (Throwable t) { /*do nothing*/ } finally { try { s.close(); } catch (Throwable t) { /*do nothing*/ } } System.err.println("Client processing finished"); } private void writeResponse(String s) throws Throwable { String response = "HTTP/1.1 200 OK\r\n" + "Server: YarServer/2009-09-09\r\n" + "Content-Type: text/javascript\r\n" + "Content-Length: " + s.length() + "\r\n" + "Connection: close\r\n\r\n"; String result = response + s; os.write(result.getBytes()); os.flush(); } private void readInputHeaders() throws Throwable { BufferedReader br = new BufferedReader(new InputStreamReader(is)); String data = ""; String action = ""; String src = ""; String volume = ""; while(true) { String s = br.readLine(); //System.err.println(s); //data += s+"\n"; if (s.startsWith("GET /favicon.ico")) return; if (s.startsWith("GET /?")) { s = s.replace("GET /?", "").replace(" HTTP/1.1", ""); String[] arr = s.split("&"); for (String str: arr) { str = URLDecoder.decode(str, "UTF-8"); if (str.startsWith("action=")) action = str.replace("action=", ""); if (str.startsWith("src=")) src = str.replace("src=", ""); if (str.startsWith("volume=")) volume = str.replace("volume=", ""); } } if(s == null || s.trim().length() == 0) { break; } } // broadcast Intent in = new Intent("Player"); in.putExtra("action", action); in.putExtra("src",src); in.putExtra("volume", volume); sendBroadcast(in); } } }
PlayerServiceは音楽の再生を担当します。
public int onStartCommand(Intent intent, int flags, int startId) { player = new MediaPlayer(); player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mp.start(); result = "ok"; status = "playing"; } }); player.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { //player.stop(); result = "ok"; if (mp.getCurrentPosition()>mp.getDuration()-2500) status = "finished"; } }); // HttpService filter = new IntentFilter("Player"); receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getStringExtra("action"); if (action.equals("play")) { player.start(); status = "playing"; } if (action.equals("pause")) { player.pause(); status = "paused"; } if (action.equals("changesrc")) { src = intent.getStringExtra("src"); try { status = "preparing"; player.reset(); //host - player.setDataSource(host+src); player.prepare(); //player.start(); result = "ok"; } catch (Exception e) { result = "error"; } } if (action.equals("setvolume")) { float volume = Float.parseFloat(intent.getStringExtra("volume")); player.setVolume(volume, volume); } } }; registerReceiver(receiver, filter); // 100 HttpService t = new Thread() { public void run() { while (true) { in = new Intent("Http"); in.putExtra("result", result); //in.putExtra("status", (player.isPlaying()?"playing":"paused")); in.putExtra("status", status); in.putExtra("currentTime", Integer.valueOf(player.getCurrentPosition()/1000).toString()); in.putExtra("duration", Integer.valueOf(player.getDuration()/1000).toString()); sendBroadcast(in); try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block //e.printStackTrace(); } } } }; t.start(); return START_STICKY; }
対応するフロントエンドコードをわずかに変更するだけです。
// $('li.track').click(function(){ ........ // window.audioServer - var url = window.audioServer+'/?action=changesrc&src='+track.mp3+'&t='+(new Date().getTime()); $.ajax({ url: encodeURI(url), type: 'GET', crossDomain: true, dataType: 'jsonp' }); ......... } // $('#volume_slider').slider({orientation:'vertical',range:'min',min:0,max:100,value:100,stop:function(event,ui){ var volume = (ui.value*1.0)/100.0; //$('#my_audio').prop('volume',volume); window.setVolume(volume); } }); window.setVolume = function(volume) { var url = window.audioServer+'/?action=setvolume&volume='+volume +'&t='+(new Date().getTime()); $.ajax({ url: encodeURI(url), type: 'GET', crossDomain: true, dataType: 'jsonp' }); }; // window.startUpdate = function() { window.update_interval = setInterval(function(){ var url = window.audioServer+'/?action=update'+'&t='+(new Date().getTime());; $.ajax({ url: encodeURI(url), type: 'GET', crossDomain: true, dataType: 'jsonp' }); if (window.result=='error') { //alert(''); } else if (window.result=='ok') { if (window.status=='playing') { if (!window.fin) { window.fin = 0; } else { window.fin--; } window.updateTime(window.currentTime, window.duration); } else if ((window.fin==0)&&(window.status=='finished')) { window.fin = 2; $('#fwd').trigger('click'); } } },500); };
結論:スピーカーでの音楽の再生は、ホームネットワークに接続されているどのデバイスからでも制御できます。