OSDN Git Service

Version 0.6.113, fix X.Net.XHR, etc...
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 02_XHTMLAudio.js
1 \r
2 /*\r
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
5  */\r
6 \r
7 var X_Audio_HTML5Audio, X_Audio_HTML5AudioWrapper, X_Audio_rawAudio,\r
8         X_Audio_HTML5Audio_LIVE_LIST = [],\r
9         X_Audio_HTML5Audio_POOL_LIST = [];\r
10         \r
11 \r
12 if( window.HTMLAudioElement ){\r
13         function getHTML5AudioWrapper( proxy ){\r
14                 var i = X_Audio_HTML5Audio_LIVE_LIST.length;\r
15                 for( ; i; ){\r
16                         if( X_Audio_HTML5Audio_LIVE_LIST[ --i ].proxy === proxy ) return X_Audio_HTML5Audio_LIVE_LIST[ i ];\r
17                 };\r
18         };\r
19         \r
20         X_Audio_HTML5Audio = \r
21                 {\r
22                         backendName : 'HTML5 Audio',\r
23                 /*\r
24                  * HTML5 の audio 要素と video 要素でサポートされているメディアフォーマット\r
25                  * https://developer.mozilla.org/ja/docs/Web/HTML/Supported_media_formats\r
26                  * \r
27                  * 主要ブラウザのHTML5 audioタグで使えるファイル形式の再生対応状況を調べてみた\r
28                  * http://sothis.blog.so-net.ne.jp/2010-10-27\r
29                  * ダメ元で仕様に含まれていない SHOUTcast もテストしてみました。\r
30                  * \r
31                  * IE9 の HTML5 Audio について\r
32                  * http://kentablog.cluscore.com/2011/05/ie9-html5-audio.html\r
33                  * 1.Audioオブジェクトを作ることができないので、Audioタグを使う\r
34                  * 2.クロスドメインアクセスには、「clientaccesspolicy.xml」か「crossdomain.xml」が必要\r
35                  * 3.wav が不可\r
36                  * \r
37                  * IE9でHTML5 autio タグが無効になる\r
38                  * http://bbs.wankuma.com/index.cgi?mode=al2&namber=64886&KLOG=109\r
39                  *  IEのバージョン9.0.8112.16421では、Audioオブジェクトのnewも対応してました。\r
40                  *  createElement等で動的生成すると、よろしくない\r
41                  * \r
42                  * media-can-play-wav-audio.html\r
43                  * https://github.com/adobe/webkit/blob/master/LayoutTests/media/media-can-play-wav-audio.html\r
44                  * testExpected("audio.canPlayType('audio/wav; codecs=1')", "probably");\r
45                  * \r
46                  * HTML5 audioタグ ブラウザ間の違い\r
47                  * 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
48                  *  - volume, muted iPhone(iOS4-6)、Android(2.3.6)では動作せず。\r
49                  *  - FireFox3.6, Android 2.3.6については、src変更後、load()を呼び出さないと切り替わらなかった。iPhoneはload()が不要。\r
50                  */     \r
51                         detect : function( proxy, source, ext ){\r
52                                 var ok, mineType = 'audio/' + ext;\r
53                                 switch( ext ){\r
54                                         case 'mp3' :\r
55                                                 ok = X_UA.IE || X_UA.Chrome || ( X_UA.Windows && X_UA.Safari );\r
56                                                 mineType = 'audio/mpeg';\r
57                                                 break;\r
58                                         case 'ogg' :\r
59                                                 ok = 15 <= X_UA.Gecko || X_UA.Chrome || X_UA.Opera;\r
60                                                 break;\r
61                                         case 'm4a' :\r
62                                                 ok = X_UA.IE || X_UA.WebKit;\r
63                                                 mineType = 'audio/mp4';\r
64                                                 break;\r
65                                         case 'webm' :\r
66                                                 ok = 2 <= X_UA.Gecko || 10.6 <= X_UA.Opera; // firefox4+(Gecko2+)\r
67                                                 break;\r
68                                         case 'wav' :\r
69                                                 ok = X_UA.Gecko || X_UA.Opera || ( X_UA.Windows && X_UA.Safari );\r
70                                                 //mineType = 'audio/wav'; // audio/x-wav ?\r
71                                                 break;\r
72                                         default :\r
73                                                 mineType = '';\r
74                                 };\r
75                                 \r
76                                 if( !ok && mineType ){\r
77                                         if( !X_Audio_rawAudio ) X_Audio_rawAudio = new Audio;\r
78                                         ok = X_Audio_rawAudio.canPlayType( mineType );\r
79                                 };\r
80                                 \r
81                                 proxy.asyncDispatch( ok ? 'support' : 'nosupport' );\r
82                         },\r
83                         \r
84                         register : function( proxy, source, option ){\r
85                                 X_Audio_HTML5Audio_LIVE_LIST.push( new X_Audio_HTML5AudioWrapper( proxy, source, option ) );\r
86                         },\r
87                         \r
88                         close : function( proxy ){\r
89                                 getHTML5AudioWrapper( proxy ).close();\r
90                         },\r
91                         \r
92                         play : function( proxy ){\r
93                                 getHTML5AudioWrapper( proxy ).play();\r
94                         },\r
95                         \r
96                         pause : function( proxy ){\r
97                                 getHTML5AudioWrapper( proxy ).pause();\r
98                         },\r
99         \r
100                         state : function( proxy, obj ){\r
101                                 return getHTML5AudioWrapper( proxy ).state( obj );\r
102                         }\r
103                 };\r
104         \r
105         X_Audio_BACKENDS.push( X_Audio_HTML5Audio );\r
106         \r
107         X_Audio_HTML5AudioWrapper = X.EventDispatcher.inherits(\r
108                 'X.AV.HTML5AudioWrapper',\r
109                 X.Class.POOL_OBJECT,\r
110                 {\r
111                         \r
112                         proxy           : null,\r
113                         \r
114                         startTime       : 0,\r
115                         endTime         : 1 / 0,\r
116                         loopStartTime   : 0,\r
117                         seekTime        : 0,\r
118                         duration        : 1 / 0,\r
119                         \r
120                         playing         : false,\r
121                         error           : 0,                    \r
122                         loop            : false,\r
123                         volume          : 0.5,\r
124 \r
125             _timerID        : 0,\r
126                         \r
127                         _closed         : true,\r
128                         \r
129                         Constructor : function( proxy, source, option ){\r
130                                 this.proxy   = proxy;\r
131                                 this._closed = false;\r
132                                 \r
133                                 X_AudioWrapper_updateStates( this, option );\r
134                                 \r
135                                 this._rawObject = X_Audio_rawAudio || new Audio( source );// X_Doc_create( 'audio', { src : source } ).appendToRoot();//( X.X_Node_systemNode );\r
136                                 \r
137                                 this.listen( [\r
138                                                 'loadstart', 'load', 'progress', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'play', 'pause', 'loadedmetadata',\r
139                                                 'loadeddata', 'waiting', 'playing', 'canplay', 'canplaythrough', 'seeking', 'seeked', 'timeupdate', 'ended',\r
140                                                 'ratechange', 'durationchange', 'volumechange' ], this.handleEventProxy );                              \r
141                                 \r
142                                 if( X_Audio_rawAudio ){\r
143                                         X_Audio_rawAudio.src = source;\r
144                                         X_Audio_rawAudio.load(); // 要る?\r
145                                         X_Audio_rawAudio = null;\r
146                                 };\r
147 \r
148                                 //document.body.appendChild( this._rawObject );\r
149 \r
150                                 this._rawObject.autoplay = false;\r
151                                 option.autoplay && X.Timer.once( 100, this, this.play );\r
152                                 \r
153                                 this.listenOnce( X.Event.KILL_INSTANCE );\r
154                         },\r
155                         \r
156                         handleEvent : function( e ){\r
157                                 switch( e.type ){\r
158 \r
159                                         case X.Event.KILL_INSTANCE :\r
160                                                 break;\r
161                                 };\r
162                         },\r
163                         /*\r
164                          * http://uguisu.skr.jp/html/table3.html\r
165                          */\r
166                         handleEventProxy : function( e ){\r
167                                 //console.log(e.type);\r
168                                 \r
169                                 switch( e.type ){\r
170                                         case 'loadstart' :      //      ブラウザがコンテンツの検索を開始した場合に発生\r
171                                                 break;\r
172                                         case 'progress' :       //      ブラウザがコンテンツの取得を実行した場合に発生\r
173                                                 //console.log( e.loaded + ' ' + e.total * 100 + '%' );\r
174                                                 this._rawObject.duration && console.log( this._rawObject.buffered.end(0) / this._rawObject.duration * 100 + '%' );\r
175                                                 break;\r
176                                         case 'loadedmetadata' : //      ブラウザがメディアリソースの長さと寸法を判定した場合に発生\r
177                                         case 'loadeddata' :     //      コンテンツの表示を現在の再生位置で初めて行えるようになった場合に発生\r
178                                         case 'canplay' :        //      今すぐに再生を再開できるが、バッファリングが不十分でコンテンツを最後まで表示できないと予測している場合に発生\r
179                                         case 'canplaythrough' : //      今すぐに再生を開始してもバッファリングで停止することなく最後まで表示できると予測している場合に発生\r
180                                                 this.duration = this._rawObject.duration * 1000;\r
181                                                 this.endTime  = this.duration < this.endTime ? this.duration : this.endTime;\r
182                                                 //console.log( this.duration );\r
183                                                 break;\r
184         \r
185                                         case 'suspend' :        //      ブラウザが意図的にコンテンツの取得を現在行っていない場合に発生(ダウンロードは未完了)\r
186                                         case 'abort' :          //      ダウンロードの完了前にコンテンツの取得を停止した場合に発生(この停止はエラーによるものではない)\r
187                                         case 'error' :          //      コンテンツの取得実行中にエラーが発生した場合に発生\r
188                                         case 'emptied' :        //      読み込み中に致命的なエラーが発生したか、実行状態ででload()メソッドが実行された場合に発生\r
189                                         case 'stalled' :        //      ブラウザがコンテンツの取得を試みたが、データがまだ用意されていない場合に発生\r
190                                         case 'play' :           //      再生が開始された。play()メソッドからの復帰後に発生する場合に発生\r
191                                         case 'pause' :          //      再生が一時停止された。pauseメソッドからの復帰後に発生する場合に発生\r
192 \r
193                                         case 'waiting' :        //      次のフレームが利用不可のため再生を停止したが、そのフレームがやがて利用可能になると想定している場合に発生\r
194                                         case 'playing' :        //      再生が開始された場合に発生\r
195 \r
196                                         case 'seeking' :        //      シークがtrueに変化し、イベントを発生させるのに十分な時間がシーク操作にかかっている場合に発生\r
197                                         case 'seeked' :         //      シークがfalseに変化した場合に発生\r
198                                         case 'timeupdate' :     //      通常の再生が行われ現在の再生位置の変化が起こった場合に発生\r
199                                                 break;\r
200                                         \r
201                                         case 'ended' :\r
202                                                 if( !this._closed && this.loop ){\r
203                                                         this.play();\r
204                                                 } else {\r
205                                                         this._timerID && X.Timer.remove( this._timerID );\r
206                                                         delete this._timerID;\r
207                                                         delete this.playing;\r
208                                                 };\r
209                                                 break;\r
210         \r
211                                         case 'ratechange' :     // defaultPlaybackRate属性とplaybackRate属性のどちらかが更新された場合に発生\r
212                                         case 'durationchange' : // duration属性が更新された場合に発生\r
213                                         case 'volumechange' :   // volume属性とmuted属性のどちらかが変化した場合に発生\r
214                                 };\r
215                                 //console.log( 'html5 ' + e.type + ' ' + ( this.proxy._listeners && this.proxy._listeners[ e.type ] && this.proxy._listeners[ e.type ].length ) );\r
216                                 //e.type === 'canplaythrough' && console.dir( e );\r
217                                 this.proxy.dispatch( e );\r
218                         },\r
219                         \r
220                         close : function(){\r
221                                 // 【javascript】モバイル向けブラウザでも音を鳴らしたい【WebAudio】\r
222                                 // http://ingaouhou.com/archives/3633\r
223                                 // ・使い終わったインスタンスはload()しておくとやや安定\r
224                                 this.playing && this.pause();\r
225                                 delete this._closed;\r
226                                 \r
227                                 this._rawObject.src = '';\r
228                                 this._rawObject.load();\r
229                         },\r
230                         \r
231                         play : function( seekTime ){\r
232                                 var begin, halfway;\r
233                                 \r
234                                 // もし kill 後に autoplayTimer で呼ばれても、_closed==true なので平気\r
235                                 if( this._closed ) return;\r
236 \r
237                             begin = ( seekTime || seekTime === 0 ) ? seekTime : this.playing ? this.loopStartTime : this.startTime;\r
238                             this._rawObject.currentTime = begin / 1000;\r
239                             \r
240                             if( !this.playing ){\r
241                                     if( X_UA.Chrome ){ // [CHROME][FIX] volume TODO どの version で 修正される?\r
242                                         // [!] delay\r
243                                         X.Timer.once( 0, this, this._fixForChrome );\r
244                                         this._rawObject.volume = 0;\r
245                                     } else {\r
246                                         this._rawObject.volume = this.volume;\r
247                                     };\r
248                                 this._rawObject.play();\r
249                                 this.playing = true;\r
250                             };\r
251 \r
252                     halfway = this.endTime < this.duration;\r
253                     this._timerID && X.Timer.remove( this._timerID );\r
254                     \r
255                 if( halfway ){\r
256                                         this._timerID = X.Timer.once( this.endTime - begin, this, this._onEnded );\r
257                 } else {\r
258                         delete this._timerID;\r
259                 };\r
260                 \r
261                                 if( !this._interval ){\r
262                                         this._interval = X.Timer.add( 1000, 0, this, this._onInterval );\r
263                                 };\r
264                         },\r
265                                 \r
266                                 // [CHROME][FIX] volume\r
267                                 _fixForChrome : X_UA.Chrome && function(){\r
268                                         !this._closed && ( this._rawObject.volume = this.volume );\r
269                                 },\r
270 \r
271                                 _onInterval : function(){\r
272                                         if( !this.playing ){\r
273                                                 delete this._interval;\r
274                                                 return X_Callback_UN_LISTEN;\r
275                                         };\r
276                                         this.proxy.dispatch( 'timeupdate' );\r
277                                 },\r
278                                 \r
279                                 _onEnded : function(){\r
280                                         delete this._timerID;\r
281                             if( this.playing ){\r
282                                 if( this.loop ){\r
283                                         this.play();\r
284                                 } else {\r
285                                         console.log( '中断:' + this._rawObject.currentTime + ' ' + this.endTime );\r
286                                         this.pause();\r
287                                         this.dispatch( 'ended' );\r
288                                 };\r
289                             };\r
290                                 },\r
291                         \r
292                         pause : function(){\r
293                                 this._timerID && X.Timer.remove( this._timerID );\r
294                                 delete this._timerID;\r
295                                 \r
296                             if( this.palying && !this._rawObject.error ){\r
297                                 this._rawObject.pause();\r
298                                 delete this.playing;\r
299                             };\r
300                         },\r
301         \r
302                         state : function( obj ){\r
303                                 var result;\r
304                                 \r
305                                 if( obj === undefined ){\r
306                                     return {\r
307                                         startTime     : this.startTime,\r
308                                         endTime       : this.endTime,\r
309                                         loopStartTime : this.loopStartTime,\r
310                                         currentTime   : this._rawObject.currentTime * 1000,\r
311                                         loop          : this.loop,\r
312                                         volume        : this.volume,\r
313                                         /*\r
314         http://www.w3schools.com/tags/av_prop_error.asp\r
315         1 = MEDIA_ERR_ABORTED - fetching process aborted by user\r
316     2 = MEDIA_ERR_NETWORK - error occurred when downloading\r
317     3 = MEDIA_ERR_DECODE - error occurred when decoding\r
318     4 = MEDIA_ERR_SRC_NOT_SUPPORTED - audio/video not supported\r
319     */\r
320                                         error         : this._rawObject.error || 0,   // 0, 1 ~ 4\r
321                                         playing       : this.palying && !this._rawObject.error && !this._rawObject.paused && !this._rawObject.ended,                            \r
322                                         duration      : this.duration || 0\r
323                                     };                                  \r
324                                 };\r
325                         \r
326                                 result = X_AudioWrapper_updateStates( this, obj );\r
327                             \r
328                                 if( result & 2 ){ // seek\r
329                         this.play( this.seekTime );\r
330                         delete this.seekTime;\r
331                                 } else {\r
332                                         if( result & 1 ){\r
333                                                 halfway = this.endTime < this.duration;\r
334                                                 this._timerID && X.Timer.remove( this._timerID );\r
335                                                 \r
336                                                 if( halfway ){\r
337                                                         this._timerID = X.Timer.once( this.endTime - this._rawObject.currentTime * 1000, this, this._onEnded );                                                 \r
338                                                 } else {\r
339                                                         delete this._timerID;\r
340                                                 };\r
341                                         };\r
342                                         if( result & 4 ){\r
343                                this._rawObject.volume = this.volume;\r
344                                         };\r
345                                 };\r
346                             \r
347                         }\r
348         \r
349                 }\r
350         );\r
351         \r
352 };\r
353 \r
354 \r
355 \r
356 \r
357 \r