Hatena::ブログ(Diary)

La Faïence

2011-11-14

Apacheやnginxのディレクトリ一覧にある音楽/動画ファイルをプレイリスト化するブックマークレット

自分用の音楽/動画用のサーバがあって,いたるところ,あらゆるデバイス(PC,iPadiPhone)で音楽を楽しみたいときのためのブラウザ プレイヤーです.

ApacheのIndexes(ディレクトリのリスト)をパースし,プレイリスト化,そして再生できるやつです.しかもMP3/AACのIDv3対応!

f:id:famnet:20111114002552p:image

プレイリスト化↓

f:id:famnet:20111114135912p:image

ディレクトリがあるとどんなに深い階層でも再帰的にプレイリスト化します.MP3/AAC(MP4Audio)のタグ対応なので,曲名もアーティストも出てきます.すばらしいですね!

SMBじゃiPadで再生できないし,ブラウザのインデックス画面だといろいろめんどいときにこいつをポチっとすると一発起動です.

ただしSafariGoogle ChromeでしかMP3/MP4再生はできません.

Chrome拡張つくりました

インストールはこちらから

http://www.keiyac.org/files/js5p/js5p.crx

Bookmarklet作りました

これをブックマークバーとかに登録しておくと,ファイル一覧のインデックス画面でこれを押すとプレイヤーが起動します.

注意)IE9だとSCRIPT58644: エラー c00ce514 のため操作を完了できませんでした。

とかいう意味不明のエラーでおかしくなる

javascript:(function(){var s=document.createElement('script');s.type='text/javascript';s.src='http://www.keiyac.org/files/js5p/js5p.js';document.getElementsByTagName('head')[0].appendChild(s);var wrap=document.createElement('div');wrap.id='_js5p_wp';document.getElementsByTagName('body')[0].appendChild(wrap);var c=document.createElement('LINK');c.rel='stylesheet';c.href='http://www.keiyac.org/files/js5p/js5p.css';c.type='text/css';c.media='screen';document.getElementsByTagName('head')[0].appendChild(c);})();

ブックマークレット表示

デモページとか

もちろんコピーレフトのやつです

http://www.keiyac.org/files/js5p/bookmarklet.html

ご注意

  • 動画は右下(bottom:0;right:0)で表示されます
  • MP3のID3タグを取得するために,初期ロード時サーバにものすごい勢いでアクセス(100ms間隔/ファイルごと)します
  • 著作権を守ってください
    • 公開サーバで利用しなければいいと思う
  • nginxは実は動作確認していない
  • ずっと全曲リピートだと思います.
    • play()関数の最初らへんのifを消してreturnすればリピートなくなる.
  • キーバインドないので,てきとうにイベントリスナ作れば良いと思う
  • SOP(Same Origin Policy)の制約上,下記HTMLを置くサーバとファイルを置くサーバは同じにしないとダメです

ソース

download

<!DOCTYPE html>
<!--
js5player v. 1.0.0 keiyac https://keiyac.appspot.com/
Copyright (C) 2011 Keiya Chinen
-->
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.9.0/build/reset/reset-min.css" /> 
<style>
header {
	position:fixed;
	top:0;
	left:5%;
	height:50px;
	width:100%;
	color:#f88;
	font-size:2em;
}
html {
	height:100%;
	width:100%;
	min-width:768px;
	background-color:#000;
	color:#eee;
	font-family:sans-serif;
}
a,a:visited {
	color:#eee;
}
#resource_index {
	display:none;
}
#sidebar {
	position:fixed;
	top:50px;
	left:0;
	width:20%;
	background-color:#111;
}
#playlist {
	position:absolute;
	top:50px;
	left:20%;
	width:70%:
	background-color:#222;
	overflow:auto;
}

#playlist table {
	position:relative;
	width:100%;
}

#playlist tr.song {
	line-height:1.75em;
}
#playlist tr.song.playing {
	background-color:#333;
}
#playlist tr.song:hover {
	background-color:#333;
}
#playlist tr.song td {
	overflow:hidden;
	white-space:nowrap;
}
#playlist .name {
	width: 40%;
}
#playlist .artist {
	width: 20%;
}
#playlist .album {
	width: 20%;
}
#playlist .tracknumber {
	width: 20%;
}
audio {
	position:fixed;
	width:100%;
	bottom:0;
}
video {
	position:fixed;
	bottom:0;
	right:0;
}
</style>
<title>js5player</title>
<body>
<header>js5player</header>
<div id='sidebar'></div>
<div id='resource_index'></div>
<div id='playlist'></div>
<div id='player'></div>
<!--
<button id='prev' href='#';>Prev</button>
<button id='next' href='#';>Next</button>
-->
<script src='http://code.jquery.com/jquery-1.7.min.js'></script>
<script src='id3.js'></script>
<script>
(function(){
/* js5player v. 1.0.0 keiyac https://keiyac.appspot.com/
 * Copyright (C) 2011 Keiya Chinen
 */
var idx = 0;
var resource_index = 'http://192.168.24.170/~keiya/play/';
var playlist = [];
var extentions = {
	'm4a':'audio/mp4',
	'm4v':'video/mp4',
	'mp3':'audio/mpeg',
	'mp4':'video/mp4',
	'oga':'audio/ogg',
	'ogg':'audio/ogg',
	'ogv':'video/ogg',
	'webm':'video/webm',
	'wav':'audio/vnd.wave'
};
var cnt = 0;
function create_plist(base) {
	$('#resource_index').html();
	$('#resource_index').load(base+' a',function(){
		$('#resource_index a').each(function(){
			var uri = $(this).attr('href');
			var short_filename = $(this).text();
			var filename = decodeURIComponent(uri);
			var directory_regexp = new RegExp("^[^/].+/$");
			var query_regexp = new RegExp("^\\?");
			var different_origin_regexp = new RegExp("^(https?:)?//");
			var file_regexp = new RegExp("^[^/]");
			var extention_regexp = new RegExp("\\.([\\w\\d]{3,4})$");
			if (uri.match(directory_regexp)) {
				create_plist(base + uri);
			}
			else if (uri.match(query_regexp)) {
			}
			else if (uri.match(different_origin_regexp)) {
				console.log('diff '+uri);
			}
			else if (uri.match(file_regexp)) {
					uri.match(extention_regexp);
					var obj = {'uri':base+uri,'filename':filename,'ext':RegExp.$1,'mime':extentions[RegExp.$1]}
					playlist.push(obj);
			}
		});
		show_plist();
		//first_play();
	});
	return;
}
function show_plist() {
	var dom_elems = '';
	for (i in playlist) {
		if (playlist[i].mime) {
		var filename = playlist[i].filename;
		dom_elems += 
		'<tr class="song" data-idx="'+i+'">'+
		'<td class="name"><a href="#">'+filename+'</a></td>'+
		'<td class="artist"></td>'+
		'<td class="album"></td>'+
		'<td class="tracknumber"></td></tr>'
		}
	}
	$('#playlist').html('<table>'+dom_elems+'</table>');
	show_tags();
}
function show_tags() {
	var i = 1;
	$('.song').each(function(){
		var $each = $(this);
		var idx = $each.attr('data-idx');
		var uri = playlist[idx].uri;
		$each.find('a').click(function(){
			var $a = $each.find('.name a');
			play(idx);
			return false;
		});
		if (playlist[idx].mime.split('/')[0] == 'audio') {
		setTimeout(function(){
			ID3.loadTags(uri,function(){
				var tags = ID3.getAllTags(uri);
				$each.find('.name a').text(tags.title);
				$each.find('.artist').text(tags.artist);
				$each.find('.album').text(tags.album);
				$each.find('.tracknumber').text(tags.track);
			})
		},i*100);
		i++;
		}
	});
}
function play(idx) {
	if (!playlist[idx]) {
		idx = 0;
//		play();
	}
	var options = 'controls preload autoplay ';
	var mime_type = playlist[idx].mime.split('/')[0];
	if (mime_type == 'video') {
		$('#player').html('<video class="queue" src="'+playlist[idx].uri+'" '+options+' />');
		$('.song[data-idx!="'+idx+'"]').each(function(){
			$(this).removeClass('playing');
		});
		$('.song[data-idx="'+idx+'"]').addClass('playing');
		$('.queue').get(0).play();
		$('.queue').bind("ended",function(){
			idx++;
			play(idx);
		});
	}
	else if (mime_type == 'audio') {
		$('#player').html('<audio class="queue" src="'+playlist[idx].uri+'" '+options+' />');
		$('.song[data-idx!="'+idx+'"]').each(function(){
			$(this).removeClass('playing');
		});
		$('.song[data-idx="'+idx+'"]').addClass('playing');
		$('.queue').get(0).play();
		$('.queue').bind("ended",function(){
			idx++;
			play(idx);
		});
	}
}
$(window).load(function(){
create_plist(resource_index);
});

}())
</script>
</body>

ぜんぶのコード書くのに7時間くらいかかった.

Groovesharkとか意識しかけたけど,頭痛だしやめた.いちおうサイドバー領域あるので,適当にごにょってください.

ipadでも!

f:id:famnet:20111114002553p:image

20歳なりました

先月末に20歳なりました.老害の始まりです.