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 seekBar = control.find( ".ui-seekbar" ),
187 durationLabel = control.find( ".ui-durationlabel" ),
188 timestampLabel = control.find( ".ui-timestamplabel" ),
189 volumeControl = control.find( ".ui-volumecontrol" ),
190 volumeBar = volumeControl.find( ".ui-volumebar" ),
191 controlWidth = width,
192 controlHeight = control.outerHeight( true ),
194 controlOffset = null;
197 if ( view[0].nodeName === "VIDEO" ) {
198 controlOffset = control.offset();
199 controlOffset.left = offset.left;
200 controlOffset.top = offset.top + height - controlHeight;
201 control.offset( controlOffset );
204 control.width( controlWidth );
208 availableWidth = control.width() - ( buttons.outerWidth( true ) * buttons.length );
209 availableWidth -= ( parseInt( buttons.eq( 0 ).css( "margin-left" ), 10 ) + parseInt( buttons.eq( 0 ).css( "margin-right" ), 10 ) ) * buttons.length;
210 if ( !self.isVolumeHide ) {
211 availableWidth -= volumeControl.outerWidth( true );
213 seekBar.width( availableWidth );
216 if ( durationLabel && !isNaN( viewElement.duration ) ) {
217 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
220 if ( viewElement.autoplay && viewElement.paused === false ) {
221 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
224 if ( seekBar.width() < ( volumeBar.width() + timestampLabel.width() + durationLabel.width() ) ) {
225 durationLabel.hide();
227 durationLabel.show();
230 _resizeFullscreen : function ( isFullscreen ) {
233 parent = view.parent(),
234 control = view.parent().find( ".ui-multimediaview-control" ),
235 playpauseButton = control.find( ".ui-playpausebutton" ),
236 timestampLabel = control.find( ".ui-timestamplabel" ),
237 seekBar = control.find( ".ui-seekbar" ),
238 durationBar = seekBar.find( ".ui-duration" ),
239 currenttimeBar = seekBar.find( ".ui-currenttime" ),
243 if ( isFullscreen ) {
244 if ( !self.backupView ) {
246 width : view[0].style.getPropertyValue( "width" ) || "",
247 height : view[0].style.getPropertyValue( "height" ) || "",
248 position : view.css( "position" ),
249 zindex : view.css( "z-index" )
252 docWidth = $( "body" )[0].clientWidth;
253 docHeight = $( "body" )[0].clientHeight;
255 view.width( docWidth ).height( docHeight - 1 );
256 view.addClass( "ui-" + self.role + "-fullscreen" );
262 if ( !self.backupView ) {
266 view.removeClass( "ui-" + self.role + "-fullscreen" );
268 "width" : self.backupView.width,
269 "height" : self.backupView.height,
270 "position": self.backupView.position,
271 "z-index": self.backupView.zindex
273 self.backupView = null;
277 _addEvent : function () {
280 viewElement = view[0],
281 control = view.parent().find( ".ui-multimediaview-control" ),
282 playpauseButton = control.find( ".ui-playpausebutton" ),
283 timestampLabel = control.find( ".ui-timestamplabel" ),
284 durationLabel = control.find( ".ui-durationlabel" ),
285 volumeButton = control.find( ".ui-volumebutton" ),
286 volumeControl = control.find( ".ui-volumecontrol" ),
287 volumeBar = volumeControl.find( ".ui-volumebar" ),
288 volumeGuide = volumeControl.find( ".ui-guide" ),
289 volumeHandle = volumeControl.find( ".ui-handler" ),
290 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
291 seekBar = control.find( ".ui-seekbar" ),
292 durationBar = seekBar.find( ".ui-duration" ),
293 currenttimeBar = seekBar.find( ".ui-currenttime" );
295 view.bind( "loadedmetadata.multimediaview", function ( e ) {
296 if ( !isNaN( viewElement.duration ) ) {
297 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
300 }).bind( "timeupdate.multimediaview", function ( e ) {
301 self._updateSeekBar();
302 }).bind( "play.multimediaview", function ( e ) {
303 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
304 }).bind( "pause.multimediaview", function ( e ) {
305 playpauseButton.removeClass( "ui-pause-icon" ).addClass( "ui-play-icon" );
306 }).bind( "ended.multimediaview", function ( e ) {
307 if ( typeof viewElement.loop == "undefined" || viewElement.loop === "" ) {
310 }).bind( "volumechange.multimediaview", function ( e ) {
311 if ( viewElement.volume < 0.1 ) {
312 viewElement.muted = true;
313 volumeButton.removeClass( "ui-volume-icon" ).addClass( "ui-mute-icon" );
315 viewElement.muted = false;
316 volumeButton.removeClass( "ui-mute-icon" ).addClass( "ui-volume-icon" );
319 if ( !self.isVolumeHide ) {
320 self._updateVolumeState();
322 }).bind( "durationchange.multimediaview", function ( e ) {
323 if ( !isNaN( viewElement.duration ) ) {
324 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
327 }).bind( "error.multimediaview", function ( e ) {
328 switch ( e.target.error.code ) {
329 case e.target.error.MEDIA_ERR_ABORTED :
330 window.alert( 'You aborted the video playback.' );
332 case e.target.error.MEDIA_ERR_NETWORK :
333 window.alert( 'A network error caused the video download to fail part-way.' );
335 case e.target.error.MEDIA_ERR_DECODE :
336 window.alert( 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.' );
338 case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED :
339 window.alert( 'The video could not be loaded, either because the server or network failed or because the format is not supported.' );
342 window.alert( 'An unknown error occurred.' );
345 }).bind( "vclick.multimediaview", function ( e ) {
346 if ( !self.options.controls ) {
350 control.fadeToggle( "fast", function () {
351 var offset = control.offset();
352 self.isControlHide = !self.isControlHide;
353 if ( self.options.mediatype == "video" ) {
360 playpauseButton.bind( "vclick.multimediaview", function () {
363 if ( viewElement.paused ) {
369 if ( self.options.mediatype == "video" ) {
374 fullscreenButton.bind( "vclick.multimediaview", function ( e ) {
375 self.fullscreen( !self.options.fullscreen );
376 control.fadeIn( "fast" );
382 seekBar.bind( "vmousedown.multimediaview", function ( e ) {
384 duration = viewElement.duration,
385 durationOffset = durationBar.offset(),
386 durationWidth = durationBar.width(),
387 timerate = ( x - durationOffset.left ) / durationWidth,
388 time = duration * timerate;
390 viewElement.currentTime = time;
397 $( document ).bind( "vmousemove.multimediaview", function ( e ) {
399 timerate = ( x - durationOffset.left ) / durationWidth;
401 viewElement.currentTime = duration * timerate;
405 }).bind( "vmouseup.multimediaview", function () {
406 $( document ).unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
407 if ( viewElement.paused ) {
415 volumeButton.bind( "vclick.multimediaview", function () {
416 if ( self.isVolumeHide ) {
417 var view = self.element,
418 volume = viewElement.volume;
420 self.isVolumeHide = false;
422 volumeControl.fadeIn( "fast" );
423 self._updateVolumeState();
424 self._updateSeekBar();
426 self.isVolumeHide = true;
427 volumeControl.fadeOut( "fast", function () {
430 self._updateSeekBar();
434 volumeBar.bind( "vmousedown.multimediaview", function ( e ) {
435 var baseX = e.clientX,
436 volumeGuideLeft = volumeGuide.offset().left,
437 volumeGuideWidth = volumeGuide.width(),
438 volumeBase = volumeGuideLeft + volumeGuideWidth,
439 handlerOffset = volumeHandle.offset(),
440 volumerate = ( baseX - volumeGuideLeft ) / volumeGuideWidth,
441 currentVolume = ( baseX - volumeGuideLeft ) / volumeGuideWidth;
444 self._setVolume( currentVolume.toFixed( 2 ) );
449 $( document ).bind( "vmousemove.multimediaview", function ( e ) {
450 var currentX = e.clientX,
451 currentVolume = ( currentX - volumeGuideLeft ) / volumeGuideWidth;
453 self._setVolume( currentVolume.toFixed( 2 ) );
457 }).bind( "vmouseup.multimediaview", function () {
458 $( document ).unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
460 if ( self.options.mediatype == "video" ) {
466 _removeEvent : function () {
469 control = view.parent().find( ".ui-multimediaview-control" ),
470 playpauseButton = control.find( ".ui-playpausebutton" ),
471 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
472 seekBar = control.find( ".ui-seekbar" ),
473 volumeControl = control.find( ".ui-volumecontrol" ),
474 volumeBar = volumeControl.find( ".ui-volumebar" ),
475 volumeHandle = volumeControl.find( ".ui-handler" );
477 view.unbind( ".multimediaview" );
478 playpauseButton.unbind( ".multimediaview" );
479 fullscreenButton.unbind( ".multimediaview" );
480 seekBar.unbind( ".multimediaview" );
481 volumeBar.unbind( ".multimediaview" );
482 volumeHandle.unbind( ".multimediaview" );
484 _createControl : function () {
487 control = $( "<span></span>" ),
488 playpauseButton = $( "<span></span>" ),
489 seekBar = $( "<span></span>" ),
490 timestampLabel = $( "<span><p>00:00:00</p></span>" ),
491 durationLabel = $( "<span><p>00:00:00</p></span>" ),
492 volumeButton = $( "<span></span>" ),
493 volumeControl = $( "<span></span>" ),
494 volumeBar = $( "<div></div>" ),
495 volumeGuide = $( "<span></span>" ),
496 volumeValue = $( "<span></span>" ),
497 volumeHandle = $( "<span></span>" ),
498 fullscreenButton = $( "<span></span>" ),
499 durationBar = $( "<span></span>" ),
500 currenttimeBar = $( "<span></span>" );
502 control.addClass( "ui-" + self.role + "-control" );
503 playpauseButton.addClass( "ui-playpausebutton ui-button" );
504 seekBar.addClass( "ui-seekbar" );
505 timestampLabel.addClass( "ui-timestamplabel" );
506 durationLabel.addClass( "ui-durationlabel" );
507 volumeButton.addClass( "ui-volumebutton ui-button" );
508 fullscreenButton.addClass( "ui-fullscreenbutton ui-button" );
509 durationBar.addClass( "ui-duration" );
510 currenttimeBar.addClass( "ui-currenttime" );
511 volumeControl.addClass( "ui-volumecontrol" );
512 volumeBar.addClass( "ui-volumebar" );
513 volumeGuide.addClass( "ui-guide" );
514 volumeValue.addClass( "ui-value" );
515 volumeHandle.addClass( "ui-handler" );
517 seekBar.append( durationBar ).append( currenttimeBar ).append( durationLabel ).append( timestampLabel );
519 playpauseButton.addClass( "ui-play-icon" );
520 if ( view[0].muted ) {
521 $( volumeButton ).addClass( "ui-mute-icon" );
523 $( volumeButton ).addClass( "ui-volume-icon" );
526 volumeBar.append( volumeGuide ).append( volumeValue ).append( volumeHandle );
527 volumeControl.append( volumeBar );
529 control.append( playpauseButton ).append( seekBar ).append( volumeControl ).append( volumeButton );
531 if ( self.element[0].nodeName === "VIDEO" ) {
532 $( fullscreenButton ).addClass( "ui-fullscreen-on" );
533 control.append( fullscreenButton );
535 volumeControl.hide();
539 _startTimer : function ( duration ) {
548 control = view.parent().find( ".ui-multimediaview-control" ),
549 volumeControl = control.find( ".ui-volumecontrol" );
551 self.controlTimer = setTimeout( function () {
552 self.isVolumeHide = true;
553 self.isControlHide = true;
554 self.controlTimer = null;
555 volumeControl.hide();
556 control.fadeOut( "fast" );
559 _endTimer : function () {
560 if ( this.controlTimer ) {
561 clearTimeout( this.controlTimer );
562 this.controlTimer = null;
565 _convertTimeFormat : function ( systime ) {
566 var ss = parseInt( systime % 60, 10 ).toString(),
567 mm = parseInt( ( systime / 60 ) % 60, 10 ).toString(),
568 hh = parseInt( systime / 3600, 10 ).toString(),
569 time = ( ( hh.length < 2 ) ? "0" + hh : hh ) + ":" +
570 ( ( mm.length < 2 ) ? "0" + mm : mm ) + ":" +
571 ( ( ss.length < 2 ) ? "0" + ss : ss );
575 _updateSeekBar : function ( currenttime ) {
578 duration = view[0].duration,
579 control = view.parent().find( ".ui-multimediaview-control" ),
580 seekBar = control.find( ".ui-seekbar" ),
581 durationBar = seekBar.find( ".ui-duration" ),
582 currenttimeBar = seekBar.find( ".ui-currenttime" ),
583 timestampLabel = control.find( ".ui-timestamplabel" ),
584 durationOffset = durationBar.offset(),
585 durationWidth = durationBar.width(),
586 durationHeight = durationBar.height(),
589 if ( typeof currenttime == "undefined" ) {
590 currenttime = view[0].currentTime;
592 timebarWidth = parseInt( currenttime / duration * durationWidth, 10 );
593 durationBar.offset( durationOffset );
594 currenttimeBar.offset( durationOffset ).width( timebarWidth );
595 timestampLabel.find( "p" ).text( self._convertTimeFormat( currenttime ) );
597 _updateVolumeState : function () {
600 control = view.parent().find( ".ui-multimediaview-control" ),
601 volumeControl = control.find( ".ui-volumecontrol" ),
602 volumeButton = control.find( ".ui-volumebutton" ),
603 volumeBar = volumeControl.find( ".ui-volumebar" ),
604 volumeGuide = volumeControl.find( ".ui-guide" ),
605 volumeValue = volumeControl.find( ".ui-value" ),
606 volumeHandle = volumeControl.find( ".ui-handler" ),
607 handlerWidth = volumeHandle.width(),
608 handlerHeight = volumeHandle.height(),
609 volumeGuideHeight = volumeGuide.height(),
610 volumeGuideWidth = volumeGuide.width(),
614 handlerOffset = null,
615 volume = view[0].volume;
617 volumeGuideTop = parseInt( volumeGuide.offset().top, 10 );
618 volumeGuideLeft = parseInt( volumeGuide.offset().left, 10 );
619 volumeBase = volumeGuideLeft;
620 handlerOffset = volumeHandle.offset();
621 handlerOffset.top = volumeGuideTop - parseInt( ( handlerHeight - volumeGuideHeight ) / 2, 10 );
622 handlerOffset.left = volumeBase + parseInt( volumeGuideWidth * volume, 10 ) - parseInt( handlerWidth / 2, 10 );
623 volumeHandle.offset( handlerOffset );
624 volumeValue.width( parseInt( volumeGuideWidth * ( volume ), 10 ) );
626 _setVolume : function ( value ) {
627 var viewElement = this.element[0];
629 if ( value < 0.0 || value > 1.0 ) {
633 viewElement.volume = value;
635 _fitContentArea: function ( page, parent ) {
636 if ( typeof parent == "undefined" ) {
640 var $page = $( page ),
641 $content = $( ".ui-content:visible:first" ),
642 hh = $( ".ui-header:visible" ).outerHeight() || 0,
643 fh = $( ".ui-footer:visible" ).outerHeight() || 0,
644 pt = parseFloat( $content.css( "padding-top" ) ),
645 pb = parseFloat( $content.css( "padding-bottom" ) ),
646 wh = ( ( parent === window ) ? window.innerHeight : $( parent ).height() ),
647 height = wh - ( hh + fh ) - ( pt + pb );
653 width : function ( value ) {
658 if ( args.length === 0 ) {
661 if ( args.length === 1 ) {
666 height : function ( value ) {
671 if ( args.length === 0 ) {
672 return view.height();
674 if ( args.length === 1 ) {
675 view.height( value );
679 size : function ( width, height ) {
683 view.width( width ).height( height );
686 fullscreen : function ( value ) {
689 control = view.parent().find( ".ui-multimediaview-control" ),
690 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
692 option = self.options,
693 currentPage = $( ".ui-page-active" );
695 if ( args.length === 0 ) {
696 return option.fullscreen;
698 if ( args.length === 1 ) {
699 view.parents( ".ui-content" ).scrollview( "scrollTo", 0, 0 );
701 this.options.fullscreen = value;
703 currentPage.children( ".ui-header" ).hide();
704 currentPage.children( ".ui-footer" ).hide();
705 this._fitContentArea( currentPage );
706 fullscreenButton.removeClass( "ui-fullscreen-on" ).addClass( "ui-fullscreen-off" );
708 currentPage.children( ".ui-header" ).show();
709 currentPage.children( ".ui-footer" ).show();
710 this._fitContentArea( currentPage );
711 fullscreenButton.removeClass( "ui-fullscreen-off" ).addClass( "ui-fullscreen-on" );
716 refresh : function () {
721 $( document ).bind( "pagecreate create", function ( e ) {
722 $.tizen.multimediaview.prototype.enhanceWithin( e.target );
724 } ( jQuery, document, window ) );