1 /* ***************************************************************************
2 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 * ***************************************************************************
23 * Authors: Yonghwi Park <yonghwi0324.park@samsung.com>
24 * Wonseop Kim <wonseop.kim@samsung.com>
29 * MultiMediaView is a widget that lets the user view and handle multimedia contents.
30 * Video and audio elements are coded as standard HTML elements and enhanced by the
31 * MultiMediaview to make them attractive and usable on a mobile device.
34 * data-theme : Set a theme of widget.
35 * If this value is not defined, widget will use parent`s theme. (optional)
36 * data-controls : If this value is 'true', widget will use belonging controller.
37 * If this value is 'false', widget will use browser`s controller.
38 * Default value is 'true'.
39 * data-fullscreen : Set a status that fullscreen when inital start.
40 * Default value is 'false'.
44 * : Get or set the width of widget.
45 * The first argument is the width of widget.
46 * If no first argument is specified, will act as a getter.
48 * : Get or set the height of widget.
49 * The first argument is the height of widget.
50 * If no first argument is specified, will act as a getter.
51 * size( number, number )
52 * : Set a size of widget and resize a widget.
53 * The first argument is width and second argument is height.
54 * fullscreen( [boolean] )
55 * : Get or Set the status of fullscreen.
56 * If no first argument is specified, will act as a getter.
60 * create : triggered when a multimediaview is created.
65 * <video data-controls="true" style="width:100%;">
66 * <source src="media/oceans-clip.mp4" type="video/mp4" />
67 * Your browser does not support the video tag.
71 * <audio data-controls="true" style="width:100%;">
72 * <source src="media/Over the horizon.mp3" type="audio/mp3" />
73 * Your browser does not support the audio tag.
78 ( function ( $, document, window, undefined ) {
79 $.widget( "tizen.multimediaview", $.mobile.widget, {
84 initSelector : "video, audio"
86 _create : function () {
89 viewElement = view[0],
90 option = self.options,
91 role = "multimediaview",
96 isControlHide : false,
104 view.addClass( "ui-multimediaview" );
105 control = self._createControl();
107 if ( view[0].nodeName === "AUDIO" ) {
108 control.addClass( "ui-multimediaview-audio" );
112 view.wrap( "<div class='ui-multimediaview-wrap'>" ).after( control );
113 if ( option.controls ) {
114 if ( view.attr("controls") ) {
115 view.removeAttr( "controls" );
121 $( document ).bind( "pagechange.multimediaview", function ( e ) {
122 var $page = $( e.target );
123 if ( $page.find( view ).length > 0 && viewElement.autoplay ) {
127 if ( option.controls ) {
131 }).bind( "pagebeforechange.multimediaview", function ( e ) {
132 if ( viewElement.played.length !== 0 ) {
137 $( window ).bind( "resize.multimediaview orientationchange.multimediaview", function ( e ) {
138 if ( !option.controls ) {
141 var $page = $( e.target ),
142 $scrollview = view.parents( ".ui-scrollview-clip" );
144 $scrollview.each( function ( i ) {
145 if ( $.data( this, "scrollview" ) ) {
146 $( this ).scrollview( "scrollTo", 0, 0 );
150 // for maintaining page layout
151 if ( !option.fullscreen ) {
152 $( ".ui-footer:visible" ).show();
154 $( ".ui-footer" ).hide();
155 self._fitContentArea( $page );
161 _resize : function () {
162 var view = this.element,
163 parent = view.parent(),
164 control = parent.find( ".ui-multimediaview-control" ),
169 this._resizeFullscreen( this.options.fullscreen );
170 viewWidth = ( ( view[0].nodeName === "VIDEO" ) ? view.width() : parent.width() );
171 viewHeight = ( ( view[0].nodeName === "VIDEO" ) ? view.height() : control.height() );
172 viewOffset = view.offset();
174 this._resizeControl( viewOffset, viewWidth, viewHeight );
176 this._updateSeekBar();
177 this._updateVolumeState();
179 _resizeControl : function ( offset, width, height ) {
182 viewElement = view[0],
183 control = view.parent().find( ".ui-multimediaview-control" ),
184 buttons = control.find( ".ui-button" ),
185 playpauseButton = control.find( ".ui-playpausebutton" ),
186 volumeControl = control.find( ".ui-volumecontrol" ),
187 seekBar = control.find( ".ui-seekbar" ),
188 durationLabel = control.find( ".ui-durationlabel" ),
189 controlWidth = width,
190 controlHeight = control.outerHeight( true ),
192 controlOffset = null;
195 if ( view[0].nodeName === "VIDEO" ) {
196 controlOffset = control.offset();
197 controlOffset.left = offset.left;
198 controlOffset.top = offset.top + height - controlHeight;
199 control.offset( controlOffset );
202 control.width( controlWidth );
206 availableWidth = control.width() - ( buttons.outerWidth( true ) * buttons.length );
207 availableWidth -= ( parseInt( buttons.eq( 0 ).css( "margin-left" ), 10 ) + parseInt( buttons.eq( 0 ).css( "margin-right" ), 10 ) ) * buttons.length;
208 if ( !self.isVolumeHide ) {
209 availableWidth -= volumeControl.outerWidth( true );
211 seekBar.width( availableWidth );
214 if ( durationLabel && !isNaN( viewElement.duration ) ) {
215 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
218 if ( viewElement.autoplay && viewElement.paused === false ) {
219 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
222 _resizeFullscreen : function ( isFullscreen ) {
225 parent = view.parent(),
226 control = view.parent().find( ".ui-multimediaview-control" ),
227 playpauseButton = control.find( ".ui-playpausebutton" ),
228 timestampLabel = control.find( ".ui-timestamplabel" ),
229 seekBar = control.find( ".ui-seekbar" ),
230 durationBar = seekBar.find( ".ui-duration" ),
231 currenttimeBar = seekBar.find( ".ui-currenttime" ),
235 if ( isFullscreen ) {
236 if ( !self.backupView ) {
238 width : view[0].style.getPropertyValue( "width" ) || "",
239 height : view[0].style.getPropertyValue( "height" ) || "",
240 position : view.css( "position" ),
241 zindex : view.css( "z-index" )
244 docWidth = $( "body" )[0].clientWidth;
245 docHeight = $( "body" )[0].clientHeight;
247 view.width( docWidth ).height( docHeight - 1 );
248 view.addClass( "ui-" + self.role + "-fullscreen" );
254 if ( !self.backupView ) {
258 view.removeClass( "ui-" + self.role + "-fullscreen" );
260 "width" : self.backupView.width,
261 "height" : self.backupView.height,
262 "position": self.backupView.position,
263 "z-index": self.backupView.zindex
265 self.backupView = null;
269 _addEvent : function () {
272 viewElement = view[0],
273 control = view.parent().find( ".ui-multimediaview-control" ),
274 playpauseButton = control.find( ".ui-playpausebutton" ),
275 timestampLabel = control.find( ".ui-timestamplabel" ),
276 durationLabel = control.find( ".ui-durationlabel" ),
277 volumeButton = control.find( ".ui-volumebutton" ),
278 volumeControl = control.find( ".ui-volumecontrol" ),
279 volumeBar = volumeControl.find( ".ui-volumebar" ),
280 volumeGuide = volumeControl.find( ".ui-guide" ),
281 volumeHandle = volumeControl.find( ".ui-handler" ),
282 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
283 seekBar = control.find( ".ui-seekbar" ),
284 durationBar = seekBar.find( ".ui-duration" ),
285 currenttimeBar = seekBar.find( ".ui-currenttime" );
287 view.bind( "loadedmetadata.multimediaview", function ( e ) {
288 if ( !isNaN( viewElement.duration ) ) {
289 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
292 }).bind( "timeupdate.multimediaview", function ( e ) {
293 self._updateSeekBar();
294 }).bind( "play.multimediaview", function ( e ) {
295 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
296 }).bind( "pause.multimediaview", function ( e ) {
297 playpauseButton.removeClass( "ui-pause-icon" ).addClass( "ui-play-icon" );
298 }).bind( "ended.multimediaview", function ( e ) {
299 if ( typeof viewElement.loop == "undefined" || viewElement.loop === "" ) {
302 }).bind( "volumechange.multimediaview", function ( e ) {
303 if ( viewElement.volume < 0.1 ) {
304 viewElement.muted = true;
305 volumeButton.removeClass( "ui-volume-icon" ).addClass( "ui-mute-icon" );
307 viewElement.muted = false;
308 volumeButton.removeClass( "ui-mute-icon" ).addClass( "ui-volume-icon" );
311 if ( !self.isVolumeHide ) {
312 self._updateVolumeState();
314 }).bind( "durationchange.multimediaview", function ( e ) {
315 if ( !isNaN( viewElement.duration ) ) {
316 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
319 }).bind( "error.multimediaview", function ( e ) {
320 switch ( e.target.error.code ) {
321 case e.target.error.MEDIA_ERR_ABORTED :
322 window.alert( 'You aborted the video playback.' );
324 case e.target.error.MEDIA_ERR_NETWORK :
325 window.alert( 'A network error caused the video download to fail part-way.' );
327 case e.target.error.MEDIA_ERR_DECODE :
328 window.alert( 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.' );
330 case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED :
331 window.alert( 'The video could not be loaded, either because the server or network failed or because the format is not supported.' );
334 window.alert( 'An unknown error occurred.' );
337 }).bind( "vclick.multimediaview", function ( e ) {
338 if ( !self.options.controls ) {
342 control.fadeToggle( "fast", function () {
343 var offset = control.offset();
344 self.isControlHide = !self.isControlHide;
345 if ( self.options.mediatype == "video" ) {
352 playpauseButton.bind( "vclick.multimediaview", function () {
355 if ( viewElement.paused ) {
361 if ( self.options.mediatype == "video" ) {
366 fullscreenButton.bind( "vclick.multimediaview", function () {
367 self.fullscreen( !self.options.fullscreen );
368 control.fadeIn( "fast" );
372 seekBar.bind( "vmousedown.multimediaview", function ( e ) {
374 duration = viewElement.duration,
375 durationOffset = durationBar.offset(),
376 durationWidth = durationBar.width(),
377 timerate = ( x - durationOffset.left ) / durationWidth,
378 time = duration * timerate;
380 viewElement.currentTime = time;
387 $( document ).bind( "vmousemove.multimediaview", function ( e ) {
389 timerate = ( x - durationOffset.left ) / durationWidth;
391 viewElement.currentTime = duration * timerate;
395 }).bind( "vmouseup.multimediaview", function () {
396 $( document ).unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
397 if ( viewElement.paused ) {
405 volumeButton.bind( "vclick.multimediaview", function () {
406 if ( self.isVolumeHide ) {
407 var view = self.element,
408 volume = viewElement.volume;
410 self.isVolumeHide = false;
412 volumeControl.fadeIn( "fast" );
413 self._updateVolumeState();
414 self._updateSeekBar();
416 self.isVolumeHide = true;
417 volumeControl.fadeOut( "fast", function () {
420 self._updateSeekBar();
424 volumeBar.bind( "vmousedown.multimediaview", function ( e ) {
425 var baseX = e.clientX,
426 volumeGuideLeft = volumeGuide.offset().left,
427 volumeGuideWidth = volumeGuide.width(),
428 volumeBase = volumeGuideLeft + volumeGuideWidth,
429 handlerOffset = volumeHandle.offset(),
430 volumerate = ( baseX - volumeGuideLeft ) / volumeGuideWidth,
431 currentVolume = ( baseX - volumeGuideLeft ) / volumeGuideWidth;
434 self._setVolume( currentVolume.toFixed( 2 ) );
439 $( document ).bind( "vmousemove.multimediaview", function ( e ) {
440 var currentX = e.clientX,
441 currentVolume = ( currentX - volumeGuideLeft ) / volumeGuideWidth;
443 self._setVolume( currentVolume.toFixed( 2 ) );
447 }).bind( "vmouseup.multimediaview", function () {
448 $( document ).unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
450 if ( self.options.mediatype == "video" ) {
456 _removeEvent : function () {
459 control = view.parent().find( ".ui-multimediaview-control" ),
460 playpauseButton = control.find( ".ui-playpausebutton" ),
461 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
462 seekBar = control.find( ".ui-seekbar" ),
463 volumeControl = control.find( ".ui-volumecontrol" ),
464 volumeBar = volumeControl.find( ".ui-volumebar" ),
465 volumeHandle = volumeControl.find( ".ui-handler" );
467 view.unbind( ".multimediaview" );
468 playpauseButton.unbind( ".multimediaview" );
469 fullscreenButton.unbind( ".multimediaview" );
470 seekBar.unbind( ".multimediaview" );
471 volumeBar.unbind( ".multimediaview" );
472 volumeHandle.unbind( ".multimediaview" );
474 _createControl : function () {
477 control = $( "<span></span>" ),
478 playpauseButton = $( "<span></span>" ),
479 seekBar = $( "<span></span>" ),
480 timestampLabel = $( "<span><p>00:00:00</p></span>" ),
481 durationLabel = $( "<span><p>00:00:00</p></span>" ),
482 volumeButton = $( "<span></span>" ),
483 volumeControl = $( "<span></span>" ),
484 volumeBar = $( "<div></div>" ),
485 volumeGuide = $( "<span></span>" ),
486 volumeValue = $( "<span></span>" ),
487 volumeHandle = $( "<span></span>" ),
488 fullscreenButton = $( "<span></span>" ),
489 durationBar = $( "<span></span>" ),
490 currenttimeBar = $( "<span></span>" );
492 control.addClass( "ui-" + self.role + "-control" );
493 playpauseButton.addClass( "ui-playpausebutton ui-button" );
494 seekBar.addClass( "ui-seekbar" );
495 timestampLabel.addClass( "ui-timestamplabel" );
496 durationLabel.addClass( "ui-durationlabel" );
497 volumeButton.addClass( "ui-volumebutton ui-button" );
498 fullscreenButton.addClass( "ui-fullscreenbutton ui-button" );
499 durationBar.addClass( "ui-duration" );
500 currenttimeBar.addClass( "ui-currenttime" );
501 volumeControl.addClass( "ui-volumecontrol" );
502 volumeBar.addClass( "ui-volumebar" );
503 volumeGuide.addClass( "ui-guide" );
504 volumeValue.addClass( "ui-value" );
505 volumeHandle.addClass( "ui-handler" );
507 seekBar.append( durationBar ).append( currenttimeBar ).append( durationLabel ).append( timestampLabel );
509 playpauseButton.addClass( "ui-play-icon" );
510 if ( view[0].muted ) {
511 $( volumeButton ).addClass( "ui-mute-icon" );
513 $( volumeButton ).addClass( "ui-volume-icon" );
516 volumeBar.append( volumeGuide ).append( volumeValue ).append( volumeHandle );
517 volumeControl.append( volumeBar );
519 control.append( playpauseButton ).append( seekBar ).append( volumeControl ).append( volumeButton );
521 if ( self.element[0].nodeName === "VIDEO" ) {
522 $( fullscreenButton ).addClass( "ui-fullscreen-on" );
523 control.append( fullscreenButton );
525 volumeControl.hide();
529 _startTimer : function ( duration ) {
538 control = view.parent().find( ".ui-multimediaview-control" ),
539 volumeControl = control.find( ".ui-volumecontrol" );
541 self.controlTimer = setTimeout( function () {
542 self.isVolumeHide = true;
543 self.isControlHide = true;
544 self.controlTimer = null;
545 volumeControl.hide();
546 control.fadeOut( "fast" );
549 _endTimer : function () {
550 if ( this.controlTimer ) {
551 clearTimeout( this.controlTimer );
552 this.controlTimer = null;
555 _convertTimeFormat : function ( systime ) {
556 var ss = parseInt( systime % 60, 10 ).toString(),
557 mm = parseInt( ( systime / 60 ) % 60, 10 ).toString(),
558 hh = parseInt( systime / 3600, 10 ).toString(),
559 time = ( ( hh.length < 2 ) ? "0" + hh : hh ) + ":" +
560 ( ( mm.length < 2 ) ? "0" + mm : mm ) + ":" +
561 ( ( ss.length < 2 ) ? "0" + ss : ss );
565 _updateSeekBar : function ( currenttime ) {
568 duration = view[0].duration,
569 control = view.parent().find( ".ui-multimediaview-control" ),
570 seekBar = control.find( ".ui-seekbar" ),
571 durationBar = seekBar.find( ".ui-duration" ),
572 currenttimeBar = seekBar.find( ".ui-currenttime" ),
573 timestampLabel = control.find( ".ui-timestamplabel" ),
574 durationOffset = durationBar.offset(),
575 durationWidth = durationBar.width(),
576 durationHeight = durationBar.height(),
579 if ( typeof currenttime == "undefined" ) {
580 currenttime = view[0].currentTime;
582 timebarWidth = parseInt( currenttime / duration * durationWidth, 10 );
583 durationBar.offset( durationOffset );
584 currenttimeBar.offset( durationOffset ).width( timebarWidth );
585 timestampLabel.find( "p" ).text( self._convertTimeFormat( currenttime ) );
587 _updateVolumeState : function () {
590 control = view.parent().find( ".ui-multimediaview-control" ),
591 volumeControl = control.find( ".ui-volumecontrol" ),
592 volumeButton = control.find( ".ui-volumebutton" ),
593 volumeBar = volumeControl.find( ".ui-volumebar" ),
594 volumeGuide = volumeControl.find( ".ui-guide" ),
595 volumeValue = volumeControl.find( ".ui-value" ),
596 volumeHandle = volumeControl.find( ".ui-handler" ),
597 handlerWidth = volumeHandle.width(),
598 handlerHeight = volumeHandle.height(),
599 volumeGuideHeight = volumeGuide.height(),
600 volumeGuideWidth = volumeGuide.width(),
604 handlerOffset = null,
605 volume = view[0].volume;
607 volumeGuideTop = parseInt( volumeGuide.offset().top, 10 );
608 volumeGuideLeft = parseInt( volumeGuide.offset().left, 10 );
609 volumeBase = volumeGuideLeft;
610 handlerOffset = volumeHandle.offset();
611 handlerOffset.top = volumeGuideTop - parseInt( ( handlerHeight - volumeGuideHeight ) / 2, 10 );
612 handlerOffset.left = volumeBase + parseInt( volumeGuideWidth * volume, 10 ) - parseInt( handlerWidth / 2, 10 );
613 volumeHandle.offset( handlerOffset );
614 volumeValue.width( parseInt( volumeGuideWidth * ( volume ), 10 ) );
616 _setVolume : function ( value ) {
617 var viewElement = this.element[0];
619 if ( value < 0.0 || value > 1.0 ) {
623 viewElement.volume = value;
625 _fitContentArea: function ( page, parent ) {
626 if ( typeof parent == "undefined" ) {
630 var $page = $( page ),
631 $content = $( ".ui-content:visible:first" ),
632 hh = $( ".ui-header:visible" ).outerHeight() || 0,
633 fh = $( ".ui-footer:visible" ).outerHeight() || 0,
634 pt = parseFloat( $content.css( "padding-top" ) ),
635 pb = parseFloat( $content.css( "padding-bottom" ) ),
636 wh = ( ( parent === window ) ? window.innerHeight : $( parent ).height() ),
637 height = wh - ( hh + fh ) - ( pt + pb );
643 width : function ( value ) {
648 if ( args.length === 0 ) {
651 if ( args.length === 1 ) {
656 height : function ( value ) {
661 if ( args.length === 0 ) {
662 return view.height();
664 if ( args.length === 1 ) {
665 view.height( value );
669 size : function ( width, height ) {
673 view.width( width ).height( height );
676 fullscreen : function ( value ) {
679 control = view.parent().find( ".ui-multimediaview-control" ),
680 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
682 option = self.options,
683 currentPage = $( ".ui-page-active" );
685 if ( args.length === 0 ) {
686 return option.fullscreen;
688 if ( args.length === 1 ) {
689 view.parents( ".ui-content" ).scrollview( "scrollTo", 0, 0 );
691 this.options.fullscreen = value;
693 currentPage.children( ".ui-header" ).hide();
694 currentPage.children( ".ui-footer" ).hide();
695 this._fitContentArea( currentPage );
696 fullscreenButton.removeClass( "ui-fullscreen-on" ).addClass( "ui-fullscreen-off" );
698 currentPage.children( ".ui-header" ).show();
699 currentPage.children( ".ui-footer" ).show();
700 this._fitContentArea( currentPage );
701 fullscreenButton.removeClass( "ui-fullscreen-off" ).addClass( "ui-fullscreen-on" );
706 refresh : function () {
711 $( document ).bind( "pagecreate create", function ( e ) {
712 $.tizen.multimediaview.prototype.enhanceWithin( e.target );
714 } ( jQuery, document, window ) );