ホームオーディオシステムの作成

すぐに自宅のオーディオシステムで理解できる予約をします。



目的:ホームネットワーク上の任意のデバイスからの列の音楽の再生を制御します。



実装は次のようになります。音楽情報はデータベースに格納され、この情報をオンデマンドでプルするサーブレットがあります。

すべては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フロントエンド



プレーヤーのフロントエンドとしてHTML5jQueryを使用します 。 ここで興味深い点は次のとおりです。



プレイリストの形成:



 $('#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つのサービス( HttpServicePlayerService)で構成されます。



そして、より詳細に。



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); };
      
      







結論:スピーカーでの音楽の再生は、ホームネットワークに接続されているどのデバイスからでも制御できます。



All Articles