3 * original : uupaa-js HTML5Audio.js
\r
4 * https://code.google.com/p/uupaa-js/source/browse/trunk/0.8/src/Audio/HTML5Audio.js?r=568
\r
7 var X_Audio_HTMLAudio_playTrigger =
\r
8 X_UA.iOS ? 'suspend' :
\r
9 X_UA.AndroidBrowser2 ? 'stalled' : // Android 2.3.5(SBM101SH) では stalled は発生しない,,,
\r
10 X_UA.AndroidBrowser4 ? 'loadeddata' : 'canplay',
\r
11 X_Audio_HTMLAudioWrapper,
\r
13 // Opera Mobile 12 android4.4.4 & 2.3.5 は 2回目以降の currentTime へのセットで currentTime が更新されなくなるため、タイマーを使用する
\r
14 X_Audio_HTMLAudioWrapper_currentTimeFix = !!X_UA.OperaMobile || !!X_UA.OperaTablet,
\r
15 // 一方 Desktop の Opera12 は、loadeddata 等では duration が infinity で、再生後の durationchange 時に duration が判明する。
\r
16 // opera12 volume, mute の変更が2度目以降できない
\r
17 // duration 判明後には currentTime によるシークと、現在時間の取得が可能になる。
\r
18 // canplay で play() durationchange で duration が取れたら loadedmetadata->loadeddata -> canplay する
\r
19 // boombox.js に書いてあった currentTime の効かないブラウザってこいつのことみたい、、、
\r
20 // Opera12.17 Win32(XP) portable apps は勝手に再生が始まる、、、その際には timeupdate が発行されない、、、 iframe+image+audio で使わないときは破棄する、とか。
\r
21 // opera11、10.54 WinXP はまとも、、、
\r
22 // X_Audio_Sprite_handleEvent でも使用
\r
23 X_Audio_HTMLAudioWrapper_durationFix = !X_Audio_HTMLAudioWrapper_currentTimeFix && 12 < X_UA.Opera,
\r
25 X_Audio_HTMLAudioWrapper_shortPlayFix = !!X_UA.AndroidBrowser4,
\r
30 if( window.HTMLAudioElement ){
\r
32 X_Audio_rawAudio = new Audio;
\r
35 mp3 : X_Audio_rawAudio.canPlayType('audio/mpeg;'),
\r
36 opus : X_Audio_rawAudio.canPlayType('audio/ogg; codecs="opus"'),
\r
37 ogg : X_Audio_rawAudio.canPlayType('audio/ogg; codecs="vorbis"'),
\r
38 wav : X_Audio_rawAudio.canPlayType('audio/wav; codecs="1"'),
\r
39 aac : X_Audio_rawAudio.canPlayType('audio/aac;'),
\r
40 m4a : (X_Audio_rawAudio.canPlayType('audio/x-m4a;') || X_Audio_rawAudio.canPlayType('audio/m4a;') || X_Audio_rawAudio.canPlayType('audio/aac;')),
\r
41 mp4 : (X_Audio_rawAudio.canPlayType('audio/x-mp4;') || X_Audio_rawAudio.canPlayType('audio/mp4;') || X_Audio_rawAudio.canPlayType('audio/aac;')),
\r
42 weba : X_Audio_rawAudio.canPlayType('audio/webm; codecs="vorbis"')
\r
46 for( k in X_Audio_codecs ){
\r
47 v = X_Audio_codecs[ k ];
\r
48 X_Audio_codecs[ k ] = v && v !== 'no';
\r
53 X_Audio_HTMLAudioWrapper = X.EventDispatcher.inherits(
\r
54 'X.AV.HTML5AudioWrapper',
\r
55 X.Class.POOL_OBJECT,
\r
78 _playForDuration : 0,
\r
81 Constructor : function( proxy, source, option ){
\r
83 this._closed = false;
\r
85 X_AudioWrapper_updateStates( this, option );
\r
87 if( option[ 'useVideo' ] ){
\r
88 this._rawObject = document.createElement( 'video' );
\r
89 this._rawObject.preload = 'none'; // auto, metadata, none
\r
90 //this._rawObject.autoplay = false, // no-auto
\r
91 this._rawObject.loop = false;
\r
92 this._rawObject.muted = false;
\r
93 //this._rawObject.crossorigin = option[ 'crossorigin' ] || ''; //crossorigin: "anonymous", X.URL.isSameDomain() で切り替え
\r
94 this._rawObject.style.cssText = 'position:absolute;bottom:0;left:-50px;width:100px;height:100px;opacity:0;';
\r
95 this._rawObject.controls = false;
\r
96 this._rawObject.WebKitPlaysInline = true;
\r
97 this._rawObject.src = source;
\r
98 //this._rawObject.onclick = "alert('play');this.play();";
\r
99 document.body.appendChild( this._rawObject );
\r
100 //this._rawObject.load();
\r
102 this._rawObject = X_Audio_rawAudio || new Audio( source );// X_Doc_create( 'audio', { src : source } ).appendToRoot();//( X.X_Node_systemNode );
\r
103 this._rawObject.autobuffer = false;
\r
104 this._src = source;
\r
108 'loadstart', 'load', 'progress', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'play', 'pause', 'loadedmetadata',
\r
109 'loadeddata', 'waiting', 'playing', 'canplay', 'canplaythrough', 'seeking', 'seeked', 'timeupdate', 'ended',
\r
110 'ratechange', 'durationchange', 'volumechange' ], this.handleEventProxy );
\r
112 if( X_Audio_rawAudio === this._rawObject ){
\r
113 X_Audio_rawAudio.src = source;
\r
114 /*!X_Audio_Sprite_needTouchFirst && */ X_Audio_rawAudio.load(); // 要る?
\r
115 X_Audio_rawAudio = null;
\r
118 //document.body.appendChild( this._rawObject );
\r
120 this._rawObject.autoplay = false;
\r
122 this.listenOnce( X.Event.KILL_INSTANCE );
\r
125 handleEvent : function( e ){
\r
128 case X.Event.KILL_INSTANCE :
\r
133 * http://uguisu.skr.jp/html/table3.html
\r
135 handleEventProxy : function( e ){
\r
137 console.log( 'HTMLAudio ' + e.type + ' ' + this._rawObject.currentTime );
\r
140 case 'loadstart' : // ブラウザがコンテンツの検索を開始した場合に発生
\r
142 case 'progress' : // ブラウザがコンテンツの取得を実行した場合に発生
\r
143 console.log( e.loaded + ' ' + e.total * 100 + '%' );
\r
144 //this._rawObject.duration &&
\r
145 // console.dir( this._rawObject );
\r
146 //console.log( this._rawObject.buffered.end(0) / this._rawObject.duration * 100 + '%' );
\r
149 case 'canplay' : // 今すぐに再生を再開できるが、バッファリングが不十分でコンテンツを最後まで表示できないと予測している場合に発生
\r
150 if( X_Audio_HTMLAudioWrapper_durationFix && this._playForDuration === 0 ){
\r
151 console.log( 'start DurationFix for Desktop Opera12 - ' + this._rawObject.duration );
\r
152 this._playForDuration = 1;
\r
153 //this._rawObject.currentTime = 0;
\r
154 this._rawObject.play();
\r
155 this._rawObject.currentTime = this._beginTime / 1000; // 必要!
\r
158 case 'loadedmetadata' : // ブラウザがメディアリソースの長さと寸法を判定した場合に発生
\r
159 case 'loadeddata' : // コンテンツの表示を現在の再生位置で初めて行えるようになった場合に発生
\r
160 case 'canplaythrough' : // 今すぐに再生を開始してもバッファリングで停止することなく最後まで表示できると予測している場合に発生
\r
161 if( X_Audio_HTMLAudioWrapper_durationFix && this._playForDuration !== 2 ) return;
\r
162 this.duration = this._rawObject.duration * 1000;
\r
163 console.log( this.duration );
\r
166 case 'suspend' : // ブラウザが意図的にコンテンツの取得を現在行っていない場合に発生(ダウンロードは未完了)
\r
167 case 'abort' : // ダウンロードの完了前にコンテンツの取得を停止した場合に発生(この停止はエラーによるものではない)
\r
168 case 'error' : // コンテンツの取得実行中にエラーが発生した場合に発生
\r
169 case 'emptied' : // 読み込み中に致命的なエラーが発生したか、実行状態ででload()メソッドが実行された場合に発生
\r
170 case 'stalled' : // ブラウザがコンテンツの取得を試みたが、データがまだ用意されていない場合に発生
\r
174 case 'play' : // 再生が開始された。play()メソッドからの復帰後に発生する場合に発生
\r
175 case 'pause' : // 再生が一時停止された。pauseメソッドからの復帰後に発生する場合に発生
\r
176 case 'waiting' : // 次のフレームが利用不可のため再生を停止したが、そのフレームがやがて利用可能になると想定している場合に発生
\r
177 case 'playing' : // 再生が開始された場合に発生
\r
178 case 'seeking' : // シークがtrueに変化し、イベントを発生させるのに十分な時間がシーク操作にかかっている場合に発生
\r
179 case 'seeked' : // シークがfalseに変化した場合に発生
\r
180 case 'ratechange' : // defaultPlaybackRate属性とplaybackRate属性のどちらかが更新された場合に発生
\r
181 case 'volumechange' : // volume属性とmuted属性のどちらかが変化した場合に発生
\r
182 if( this._playForDuration === 1 ) return;
\r
185 case 'timeupdate' : // 通常の再生が行われ現在の再生位置の変化が起こった場合に発生
\r
186 console.log( this._rawObject.currentTime );
\r
187 if( this._playForDuration === 1 ){
\r
188 this._rawObject.currentTime = this._beginTime / 1000; // 必要!
\r
195 if( !this._closed && this.loop ){
\r
196 this.looped = true;
\r
197 ( this.proxy.dispatch( 'looped' ) & X.Callback.PREVENT_DEFAULT ) || this.play();
\r
200 delete this.playing;
\r
204 case 'durationchange' : // duration属性が更新された場合に発生
\r
206 if( !X_Audio_HTMLAudioWrapper_durationFix ){
\r
207 this.duration = this._rawObject.duration * 1000;
\r
210 if( this._rawObject.duration !== 1 / 0 ){
\r
212 console.log( this._rawObject.duration );
\r
214 this.duration = this._rawObject.duration * 1000;
\r
216 if( this._playForDuration === 0 ) this._playForDuration = 2;
\r
218 if( this._playForDuration === 1 ){
\r
219 this._playForDuration = 2;
\r
221 console.log( 'Loaded ' + this._loaded );
\r
223 if( this._loaded ){
\r
224 this._rawObject.currentTime = this._beginTime / 1000;
\r
225 console.log( '設定 ' + this._beginTime );
\r
229 this.proxy.asyncDispatch( 'loadedmetadata' );
\r
230 this.proxy.asyncDispatch( 'loadeddata' );
\r
231 this.proxy.asyncDispatch( 'canplay' );
\r
232 this.proxy.asyncDispatch( 'canplaythrough' );
\r
234 console.log( 'Desktop Opera のための currentTime と duration の fix が完了' + this.duration );
\r
236 if( this.autoplay ){
\r
237 this._rawObject.currentTime = this._beginTime / 1000;
\r
239 // Opera12.17 WinXP で勝手に再生される不具合
\r
241 this._rawObject.src = '';
\r
242 //this._rawObject.load();
\r
250 //console.log( 'html5 ' + e.type + ' ' + ( this.proxy._listeners && this.proxy._listeners[ e.type ] && this.proxy._listeners[ e.type ].length ) );
\r
251 //e.type === 'canplaythrough' && console.dir( e );
\r
252 loaded || this.proxy.dispatch( e );
\r
254 if( X_Audio_HTMLAudio_playTrigger === e.type || ( X_UA.AndroidBrowser2 && e.type === 'loadeddata' ) || loaded ){
\r
255 !this._loaded && this.autoplay && X.Timer.once( 16, this, this.play );
\r
256 this._loaded = true;
\r
257 console.log( 'Loaded! ' + e.type );
\r
261 close : function(){
\r
262 // 【javascript】モバイル向けブラウザでも音を鳴らしたい【WebAudio】
\r
263 // http://ingaouhou.com/archives/3633
\r
264 // ・使い終わったインスタンスはload()しておくとやや安定
\r
265 this.playing && this.pause();
\r
266 delete this._closed;
\r
267 delete this._loaded;
\r
269 this._rawObject.src = '';
\r
270 this._rawObject.load();
\r
276 // もし kill 後に autoplayTimer で呼ばれても、_closed==true なので平気
\r
277 if( this._closed ) return;
\r
278 if( !this._loaded /* && !X_Audio_Sprite_inTouchAction */ ){
\r
279 this.autoplay = true;
\r
283 end = X_AudioWrapper_getEndTime( this );
\r
284 begin = X_AudioWrapper_getStartTime( this, end, true );
\r
286 if( X_Audio_HTMLAudioWrapper_shortPlayFix ){
\r
287 begin -= end - begin > 1000 ? 200 : 400;
\r
288 begin = begin < 0 ? 0 : begin;
\r
291 if( !this._rawObject.src ){
\r
292 this._beginTime = begin;
\r
293 this._rawObject.src = this._src;
\r
294 delete this._playForDuration;
\r
297 this._rawObject.currentTime = begin / 1000;
\r
299 console.log( '[HTMLAudio] play ' + begin + ' -> ' + end );
\r
301 if( !this.playing ){
\r
302 if( X_UA.Chrome ){ // [CHROME][FIX] volume TODO どの version で 修正される?
\r
304 X.Timer.once( 0, this, this._fixForChrome );
\r
305 this._rawObject.volume = 0;
\r
307 this._rawObject.volume = this.volume;
\r
309 this._rawObject.play();
\r
310 this.playing = true;
\r
313 if( X_Audio_HTMLAudioWrapper_currentTimeFix ){
\r
314 this._beginTime = begin;
\r
315 this._playTime = X_Timer_now();
\r
320 // [CHROME][FIX] volume
\r
321 _fixForChrome : X_UA.Chrome && function(){
\r
322 !this._closed && ( this._rawObject.volume = this.volume );
\r
325 _onEnded : function(){
\r
326 var currentTime, time;
\r
328 if( this.playing ){
\r
329 currentTime = X_Audio_HTMLAudioWrapper_currentTimeFix ? X_Timer_now() - this._playTime + this._beginTime : this._rawObject.currentTime * 1000 | 0;
\r
330 time = currentTime - X_AudioWrapper_getEndTime( this );
\r
333 console.log( '> onEnd ' + (-time) + ' ' + currentTime + '/' + ( X_AudioWrapper_getEndTime( this ) ) );
\r
338 if( !( this.proxy.dispatch( 'looped' ) & X.Callback.PREVENT_DEFAULT ) ){
\r
339 this.looped = true;
\r
344 this.proxy.dispatch( 'ended' );
\r
349 pause : function(){
\r
350 if( !this.playing ) return;
\r
352 this.seekTime = this.state().currentTime;
\r
354 delete this._playTime;
\r
356 !this._rawObject.error && this._rawObject.pause();
\r
357 delete this.playing;
\r
360 state : function( obj ){
\r
363 if( obj === undefined ){
\r
365 startTime : this.startTime,
\r
366 endTime : this.endTime < 0 ? this.duration : this.endTime,
\r
367 loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
\r
368 loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
\r
371 looped : this.looped,
\r
372 volume : this.volume,
\r
373 playing : this.playing, // && !this._rawObject.error && !this._rawObject.paused && !this._rawObject.ended,
\r
374 duration : this.duration,
\r
378 ( X_Audio_HTMLAudioWrapper_currentTimeFix ?
\r
379 X_Timer_now() - this._playTime + this._beginTime :
\r
380 this._rawObject.currentTime * 1000 | 0 ) :
\r
383 http://www.w3schools.com/tags/av_prop_error.asp
\r
384 1 = MEDIA_ERR_ABORTED - fetching process aborted by user
\r
385 2 = MEDIA_ERR_NETWORK - error occurred when downloading
\r
386 3 = MEDIA_ERR_DECODE - error occurred when decoding
\r
387 4 = MEDIA_ERR_SRC_NOT_SUPPORTED - audio/video not supported
\r
389 error : this._rawObject.error || 0 // 0, 1 ~ 4
\r
393 result = X_AudioWrapper_updateStates( this, obj );
\r
395 if( result & 2 ){ // seek
\r
398 //if( result & 1 ){
\r
399 //if( X_Audio_HTMLAudioWrapper_currentTimeFix ){
\r
405 this._rawObject.volume = this.volume;
\r
413 X_Audio_BACKENDS.push(
\r
415 backendName : 'HTML Audio',
\r
417 * HTML5 の audio 要素と video 要素でサポートされているメディアフォーマット
\r
418 * https://developer.mozilla.org/ja/docs/Web/HTML/Supported_media_formats
\r
420 * 主要ブラウザのHTML5 audioタグで使えるファイル形式の再生対応状況を調べてみた
\r
421 * http://sothis.blog.so-net.ne.jp/2010-10-27
\r
422 * ダメ元で仕様に含まれていない SHOUTcast もテストしてみました。
\r
424 * IE9 の HTML5 Audio について
\r
425 * http://kentablog.cluscore.com/2011/05/ie9-html5-audio.html
\r
426 * 1.Audioオブジェクトを作ることができないので、Audioタグを使う
\r
427 * 2.クロスドメインアクセスには、「clientaccesspolicy.xml」か「crossdomain.xml」が必要
\r
430 * IE9でHTML5 autio タグが無効になる
\r
431 * http://bbs.wankuma.com/index.cgi?mode=al2&namber=64886&KLOG=109
\r
432 * IEのバージョン9.0.8112.16421では、Audioオブジェクトのnewも対応してました。
\r
433 * createElement等で動的生成すると、よろしくない
\r
435 * media-can-play-wav-audio.html
\r
436 * https://github.com/adobe/webkit/blob/master/LayoutTests/media/media-can-play-wav-audio.html
\r
437 * testExpected("audio.canPlayType('audio/wav; codecs=1')", "probably");
\r
439 * HTML5 audioタグ ブラウザ間の違い
\r
440 * http://wiki.bit-hive.com/tomizoo/pg/HTML5%20audio%A5%BF%A5%B0%20%A5%D6%A5%E9%A5%A6%A5%B6%B4%D6%A4%CE%B0%E3%A4%A4
\r
441 * - volume, muted iPhone(iOS4-6)、Android(2.3.6)では動作せず。
\r
442 * - FireFox3.6, Android 2.3.6については、src変更後、load()を呼び出さないと切り替わらなかった。iPhoneはload()が不要。
\r
444 detect : function( proxy, source, ext ){
\r
446 var ok, mineType = 'audio/' + ext;
\r
449 ok = X_UA.IE || X_UA.Chrome || ( X_UA.Windows && X_UA.Safari );
\r
450 mineType = 'audio/mpeg';
\r
451 //if( X_UA.Android && X_UA.Gecko ) mineType = '';
\r
454 ok = 15 <= X_UA.Gecko || X_UA.Chrome || X_UA.Opera;
\r
455 if( X_UA.AndroidBrowser ) mineType = '';
\r
458 ok = X_UA.IE || X_UA.WebKit;
\r
459 mineType = 'audio/mp4';
\r
462 ok = 2 <= X_UA.Gecko || 10.6 <= X_UA.Opera; // firefox4+(Gecko2+)
\r
465 ok = X_UA.Gecko || X_UA.Opera || ( X_UA.Windows && X_UA.Safari );
\r
466 //mineType = 'audio/wav'; // audio/x-wav ?
\r
472 if( !ok && mineType ){
\r
473 if( !X_Audio_rawAudio ) X_Audio_rawAudio = new Audio;
\r
474 ok = X_Audio_rawAudio.canPlayType( mineType );
\r
477 proxy.asyncDispatch( ok ? 'support' : 'nosupport' ); */
\r
479 proxy.asyncDispatch( X_Audio_codecs[ ext ] ? 'support' : 'nosupport' );
\r
482 klass : X_Audio_HTMLAudioWrapper
\r
490 mp3: !!audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''),
\r
491 opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''),
\r
492 ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''),
\r
493 wav: !!audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ''),
\r
494 aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''),
\r
495 m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),
\r
496 mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),
\r
497 weba: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')
\r