2 /* ***************************************************************************
3 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 * ***************************************************************************
24 * Authors: Yonghwi Park <yonghwi0324.park@samsung.com>
25 * Wonseop Kim <wonseop.kim@samsung.com>
30 * MultiMediaView is a widget that lets the user view and handle multimedia contents.
31 * Video and audio elements are coded as standard HTML elements and enhanced by the
32 * MultiMediaview to make them attractive and usable on a mobile device.
35 * data-theme : Set a theme of widget.
36 * If this value is not defined, widget will use parent`s theme. (optional)
37 * data-controls : If this value is 'true', widget will use belonging controller.
38 * If this value is 'false', widget will use browser`s controller.
39 * Default value is 'true'.
40 * data-full-screen : Set a status that full-screen when inital start.
41 * Default value is 'false'.
45 * : Get or set the width of widget.
46 * The first argument is the width of widget.
47 * If no first argument is specified, will act as a getter.
49 * : Get or set the height of widget.
50 * The first argument is the height of widget.
51 * If no first argument is specified, will act as a getter.
52 * fullScreen( [boolean] )
53 * : Get or Set the status of full-screen.
54 * If no first argument is specified, will act as a getter.
63 * <video data-controls="true" style="width:100%;">
64 * <source src="media/oceans-clip.mp4" type="video/mp4" />
65 * Your browser does not support the video tag.
69 * <audio data-controls="true" style="width:100%;">
70 * <source src="media/Over the horizon.mp3" type="audio/mp3" />
71 * Your browser does not support the audio tag.
77 The multimedia view widget shows a player control that you can use to view and handle multimedia content. This widget uses the standard HTML video and audio elements, which have been enhanced for use on a mobile device.
79 To add a multimedia view widget to the application, use the following code:
81 // Video player control
82 <video data-controls="true" style="width:100%;">
83 <source src="<VIDEO_FILE_URL>" type="video/mp4" /> Your browser does not support the video tag. </video>
84 // Audio player control
85 <audio data-controls="true" style="width:100%;"> <source src="<AUDIO_FILE_URL>" type="audio/mp3" /> Your browser does not support the audio tag.
89 @property {Boolean} data-control
90 Sets the controls for the widget.
91 The default value is true. If the value is set to true, the widget uses its own player controls. If the value is set to false, the widget uses the browser's player controls.
94 @property {Boolean} data-full-screen
95 Defines whether the widget opens in the fullscreen view mode.
96 The default value is false.
99 @property {String} data-theme
100 Sets the widget theme.
101 If the value is not set, the parent control's theme is used
105 The width method is used to get (if no value is defined) or set the multimedia view widget width:
107 <source src="test.mp4" type="video/mp4" />
109 $(".selector").multimediaview("width", [value]);
113 The height method is used to get (if no value is defined) or set the multimedia view widget height:
115 <source src="test.mp4" type="video/mp4" />
117 $(".selector").multimediaview("height", [value]);
121 The fullScreen method is used to get (if no value is defined) or set the full-screen mode of the multimedia view widget. If the value is true, the full-screen mode is used; otherwise the multimedia view widget runs in the normal mode.
124 <source src="test.mp4" type="video/mp4" />
126 $(".selector").multimediaview("fullScreen", [value]);
128 ( function ( $, document, window, undefined ) {
129 $.widget( "tizen.multimediaview", $.mobile.widget, {
134 initSelector: "video, audio"
137 _create: function () {
140 viewElement = view[0],
141 isVideo = ( viewElement.nodeName === "VIDEO" ),
142 option = self.options,
143 parentTheme = $.mobile.getInheritedTheme( view, "s" ),
144 theme = option.theme || parentTheme,
145 width = viewElement.style.getPropertyValue( "width" ) || "",
146 wrap = $( "<div class='ui-multimediaview-wrap ui-multimediaview-" + theme + "'>" ),
158 view.addClass( "ui-multimediaview" );
159 control = self._createControl();
162 control.find( ".ui-button" ).each( function ( index ) {
163 $( this ).buttonMarkup( { corners: true, theme: theme, shadow: true } );
166 view.wrap( wrap ).after( control );
169 control.addClass( "ui-multimediaview-video" );
172 self.options.fullScreen = false;
175 if ( option.controls && view.attr( "controls" ) ) {
176 view.removeAttr( "controls" );
182 _resize: function () {
183 this._resizeFullscreen( this.options.fullScreen );
184 this._resizeControl();
185 this._updateSeekBar();
186 this._updateVolumeState();
189 _resizeControl: function () {
192 viewElement = view[0],
193 isVideo = self._isVideo,
194 wrap = view.parent( ".ui-multimediaview-wrap" ),
195 control = wrap.find( ".ui-multimediaview-control" ),
196 buttons = control.find( ".ui-button" ),
197 playpauseButton = control.find( ".ui-playpausebutton" ),
198 seekBar = control.find( ".ui-seekbar" ),
199 durationLabel = control.find( ".ui-durationlabel" ),
200 timestampLabel = control.find( ".ui-timestamplabel" ),
201 volumeControl = control.find( ".ui-volumecontrol" ),
202 volumeBar = volumeControl.find( ".ui-volumebar" ),
203 width = ( isVideo ? view.width() : wrap.width() ),
204 height = ( isVideo ? view.height() : control.height() ),
205 offset = view.offset(),
206 controlHeight = control.height(),
208 controlOffset = null;
212 controlOffset = control.offset();
213 controlOffset.left = offset.left;
214 controlOffset.top = offset.top + height - controlHeight;
215 control.offset( controlOffset );
217 control.width( width );
221 availableWidth = control.width() - ( buttons.outerWidth( true ) * buttons.length );
222 availableWidth -= ( parseInt( buttons.eq( 0 ).css( "margin-left" ), 10 ) + parseInt( buttons.eq( 0 ).css( "margin-right" ), 10 ) ) * buttons.length;
223 if ( !self.isVolumeHide ) {
224 availableWidth -= volumeControl.outerWidth( true );
226 seekBar.width( availableWidth );
229 if ( durationLabel && !isNaN( viewElement.duration ) ) {
230 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
233 if ( viewElement.autoplay && viewElement.paused === false ) {
234 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
237 if ( seekBar.width() < ( volumeBar.width() + timestampLabel.width() + durationLabel.width() ) ) {
238 durationLabel.hide();
240 durationLabel.show();
244 _resizeFullscreen: function ( isFullscreen ) {
245 if ( !this._isVideo ) {
251 viewElement = view[0],
252 wrap = view.parent( ".ui-multimediaview-wrap" ),
253 control = wrap.find( ".ui-multimediaview-control" ),
254 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
255 currentPage = $( ".ui-page-active" ),
256 playpauseButton = control.find( ".ui-playpausebutton" ),
257 timestampLabel = control.find( ".ui-timestamplabel" ),
258 seekBar = control.find( ".ui-seekbar" ),
259 durationBar = seekBar.find( ".ui-duration" ),
260 currenttimeBar = seekBar.find( ".ui-currenttime" ),
261 body = $( "body" )[0],
262 header = currentPage.children( ".ui-header" ),
263 footer = currentPage.children( ".ui-footer" ),
267 if ( isFullscreen ) {
268 if ( !self.backupView ) {
270 width: viewElement.style.getPropertyValue( "width" ) || "",
271 height: viewElement.style.getPropertyValue( "height" ) || "",
272 position: view.css( "position" ),
273 zindex: view.css( "z-index" ),
274 wrapHeight: wrap[0].style.getPropertyValue( "height" ) || ""
277 docWidth = body.clientWidth;
278 docHeight = body.clientHeight - 1;
282 view.parents().each( function ( e ) {
283 var element = $( this );
284 element.addClass( "ui-fullscreen-parents" )
286 .addClass( "ui-multimediaview-siblings-off" );
288 fullscreenButton.removeClass( "ui-fullscreen-on" ).addClass( "ui-fullscreen-off" );
290 wrap.height( docHeight );
291 view.width( docWidth ).height( docHeight );
293 if ( !self.backupView ) {
299 view.parents().each( function ( e ) {
300 var element = $( this );
301 element.removeClass( "ui-fullscreen-parents" )
303 .removeClass( "ui-multimediaview-siblings-off" );
306 fullscreenButton.removeClass( "ui-fullscreen-off" ).addClass( "ui-fullscreen-on" );
308 wrap.css( "height", self.backupView.wrapHeight );
310 "width": self.backupView.width,
311 "height": self.backupView.height,
312 "position": self.backupView.position,
313 "z-index": self.backupView.zindex
315 self.backupView = null;
317 $( window ).trigger( "throttledresize" );
321 _addEvent: function () {
324 option = self.options,
325 viewElement = view[0],
326 isVideo = self._isVideo,
327 control = view.parent( ".ui-multimediaview-wrap" ).find( ".ui-multimediaview-control" ),
328 playpauseButton = control.find( ".ui-playpausebutton" ),
329 timestampLabel = control.find( ".ui-timestamplabel" ),
330 durationLabel = control.find( ".ui-durationlabel" ),
331 volumeButton = control.find( ".ui-volumebutton" ),
332 volumeControl = control.find( ".ui-volumecontrol" ),
333 volumeBar = volumeControl.find( ".ui-volumebar" ),
334 volumeGuide = volumeControl.find( ".ui-guide" ),
335 volumeHandle = volumeControl.find( ".ui-handle" ),
336 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
337 seekBar = control.find( ".ui-seekbar" ),
338 durationBar = seekBar.find( ".ui-duration" ),
339 currenttimeBar = seekBar.find( ".ui-currenttime" ),
340 $document = $( document );
342 view.bind( "loadedmetadata.multimediaview", function ( e ) {
343 if ( !isNaN( viewElement.duration ) ) {
344 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
347 }).bind( "timeupdate.multimediaview", function ( e ) {
348 self._updateSeekBar();
349 }).bind( "play.multimediaview", function ( e ) {
350 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
351 }).bind( "pause.multimediaview", function ( e ) {
352 playpauseButton.removeClass( "ui-pause-icon" ).addClass( "ui-play-icon" );
353 }).bind( "ended.multimediaview", function ( e ) {
354 if ( typeof viewElement.loop == "undefined" || viewElement.loop === "" ) {
357 }).bind( "volumechange.multimediaview", function ( e ) {
358 if ( viewElement.muted && viewElement.volume > 0.1 ) {
359 volumeButton.removeClass( "ui-volume-icon" ).addClass( "ui-mute-icon" );
360 self._reserveVolume = viewElement.volume;
361 viewElement.volume = 0;
362 } else if ( self._reserveVolume !== -1 && !viewElement.muted ) {
363 volumeButton.removeClass( "ui-mute-icon" ).addClass( "ui-volume-icon" );
364 viewElement.volume = self._reserveVolume;
365 self._reserveVolume = -1;
366 } else if ( viewElement.volume < 0.1 ) {
367 volumeButton.removeClass( "ui-volume-icon" ).addClass( "ui-mute-icon" );
369 volumeButton.removeClass( "ui-mute-icon" ).addClass( "ui-volume-icon" );
372 if ( !self.isVolumeHide ) {
373 self._updateVolumeState();
375 }).bind( "durationchange.multimediaview", function ( e ) {
376 if ( !isNaN( viewElement.duration ) ) {
377 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
380 }).bind( "click.multimediaview", function ( e ) {
381 if ( !self.options.controls ) {
385 control.fadeToggle( "fast" );
387 }).bind( "multimediaviewinit", function ( e ) {
388 if ( option.controls ) {
394 playpauseButton.bind( "click.multimediaview", function () {
397 if ( viewElement.paused ) {
408 fullscreenButton.bind( "click.multimediaview", function ( e ) {
410 self.fullScreen( !self.options.fullScreen );
416 seekBar.bind( "vmousedown.multimediaview", function ( e ) {
418 duration = viewElement.duration,
419 durationOffset = durationBar.offset(),
420 durationWidth = durationBar.width(),
421 timerate = ( x - durationOffset.left ) / durationWidth,
422 time = duration * timerate;
424 if ( !viewElement.played.length ) {
428 viewElement.currentTime = time;
434 $document.bind( "vmousemove.multimediaview", function ( e ) {
436 timerate = ( x - durationOffset.left ) / durationWidth;
438 viewElement.currentTime = duration * timerate;
441 }).bind( "vmouseup.multimediaview", function () {
442 $document.unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
443 if ( viewElement.paused ) {
451 volumeButton.bind( "click.multimediaview", function () {
452 if ( self.isVolumeHide ) {
453 var view = self.element,
454 volume = viewElement.volume;
456 self.isVolumeHide = false;
457 volumeControl.fadeIn( "fast", function () {
458 self._updateVolumeState();
459 self._updateSeekBar();
463 self.isVolumeHide = true;
464 volumeControl.fadeOut( "fast", function () {
470 volumeBar.bind( "vmousedown.multimediaview", function ( e ) {
471 var baseX = e.clientX,
472 volumeGuideLeft = volumeGuide.offset().left,
473 volumeGuideWidth = volumeGuide.width(),
474 volumeBase = volumeGuideLeft + volumeGuideWidth,
475 handlerOffset = volumeHandle.offset(),
476 volumerate = ( baseX - volumeGuideLeft ) / volumeGuideWidth,
477 currentVolume = ( baseX - volumeGuideLeft ) / volumeGuideWidth;
480 self._setVolume( currentVolume.toFixed( 2 ) );
484 $document.bind( "vmousemove.multimediaview", function ( e ) {
485 var currentX = e.clientX,
486 currentVolume = ( currentX - volumeGuideLeft ) / volumeGuideWidth;
488 self._setVolume( currentVolume.toFixed( 2 ) );
491 }).bind( "vmouseup.multimediaview", function () {
492 $document.unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
497 _removeEvent: function () {
498 var view = this.element,
499 control = view.parent( ".ui-multimediaview-wrap" ).find( ".ui-multimediaview-control" ),
500 playpauseButton = control.find( ".ui-playpausebutton" ),
501 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
502 seekBar = control.find( ".ui-seekbar" ),
503 volumeControl = control.find( ".ui-volumecontrol" ),
504 volumeBar = volumeControl.find( ".ui-volumebar" ),
505 volumeHandle = volumeControl.find( ".ui-handle" );
507 view.unbind( ".multimediaview" );
508 playpauseButton.unbind( ".multimediaview" );
509 fullscreenButton.unbind( ".multimediaview" );
510 seekBar.unbind( ".multimediaview" );
511 volumeBar.unbind( ".multimediaview" );
512 volumeHandle.unbind( ".multimediaview" );
515 _createControl: function () {
516 var view = this.element,
517 viewElement = view[0],
518 control = $( "<span></span>" ).addClass( "ui-multimediaview-control" ),
519 playpauseButton = $( "<span></span>" ).addClass( "ui-playpausebutton ui-button ui-play-icon" ),
520 seekBar = $( "<span></span>" ).addClass( "ui-seekbar ui-multimediaview-bar" ),
521 timestampLabel = $( "<span><p>00:00:00</p></span>" ).addClass( "ui-timestamplabel" ),
522 durationLabel = $( "<span><p>00:00:00</p></span>" ).addClass( "ui-durationlabel" ),
523 volumeButton = $( "<span></span>" ).addClass( "ui-volumebutton ui-button" ),
524 volumeControl = $( "<span></span>" ).addClass( "ui-volumecontrol" ),
525 volumeBar = $( "<div></div>" ).addClass( "ui-volumebar ui-multimediaview-bar" ),
526 volumeGuide = $( "<span></span>" ).addClass( "ui-guide ui-multimediaview-bar-bg" ),
527 volumeValue = $( "<span></span>" ).addClass( "ui-value ui-multimediaview-bar-highlight" ),
528 volumeHandle = $( "<span></span>" ).addClass( "ui-handle" ),
529 fullscreenButton = $( "<span></span>" ).addClass( "ui-fullscreenbutton ui-button" ),
530 durationBar = $( "<span></span>" ).addClass( "ui-duration ui-multimediaview-bar-bg" ),
531 currenttimeBar = $( "<span></span>" ).addClass( "ui-currenttime ui-multimediaview-bar-highlight" );
533 seekBar.append( durationBar ).append( currenttimeBar ).append( durationLabel ).append( timestampLabel );
535 volumeButton.addClass( viewElement.muted ? "ui-mute-icon" : "ui-volume-icon" );
536 volumeBar.append( volumeGuide ).append( volumeValue ).append( volumeHandle );
537 volumeControl.append( volumeBar );
539 control.append( playpauseButton ).append( seekBar ).append( volumeControl ).append( volumeButton );
541 if ( this._isVideo ) {
542 $( fullscreenButton ).addClass( "ui-fullscreen-on" );
543 control.append( fullscreenButton );
545 volumeControl.hide();
550 _startTimer: function ( duration ) {
559 control = view.parent( ".ui-multimediaview-wrap" ).find( ".ui-multimediaview-control" ),
560 volumeControl = control.find( ".ui-volumecontrol" );
562 self.controlTimer = setTimeout( function () {
563 self.isVolumeHide = true;
564 self.controlTimer = null;
565 volumeControl.hide();
566 control.fadeOut( "fast" );
570 _endTimer: function () {
571 if ( this.controlTimer ) {
572 clearTimeout( this.controlTimer );
573 this.controlTimer = null;
577 _convertTimeFormat: function ( systime ) {
578 if ( !$.isNumeric( systime ) ) {
579 return "Playback Error";
582 var ss = parseInt( systime % 60, 10 ).toString(),
583 mm = parseInt( ( systime / 60 ) % 60, 10 ).toString(),
584 hh = parseInt( systime / 3600, 10 ).toString(),
585 time = ( ( hh.length < 2 ) ? "0" + hh : hh ) + ":" +
586 ( ( mm.length < 2 ) ? "0" + mm : mm ) + ":" +
587 ( ( ss.length < 2 ) ? "0" + ss : ss );
592 _updateSeekBar: function ( currenttime ) {
593 var view = this.element,
594 viewElement = view[0],
595 duration = viewElement.duration,
596 control = view.parent( ".ui-multimediaview-wrap" ).find( ".ui-multimediaview-control" ),
597 seekBar = control.find( ".ui-seekbar" ),
598 durationBar = seekBar.find( ".ui-duration" ),
599 currenttimeBar = seekBar.find( ".ui-currenttime" ),
600 timestampLabel = control.find( ".ui-timestamplabel" ),
601 durationOffset = durationBar.offset(),
602 durationWidth = durationBar.width(),
603 durationHeight = durationBar.height(),
606 if ( typeof currenttime === "undefined" ) {
607 currenttime = viewElement.currentTime;
609 timebarWidth = parseInt( currenttime / duration * durationWidth, 10 );
610 durationBar.offset( durationOffset );
611 currenttimeBar.offset( durationOffset ).width( timebarWidth );
612 timestampLabel.find( "p" ).text( this._convertTimeFormat( currenttime ) );
615 _updateVolumeState: function () {
616 var view = this.element,
617 control = view.parent( ".ui-multimediaview-wrap" ).find( ".ui-multimediaview-control" ),
618 volumeControl = control.find( ".ui-volumecontrol" ),
619 volumeButton = control.find( ".ui-volumebutton" ),
620 volumeBar = volumeControl.find( ".ui-volumebar" ),
621 volumeGuide = volumeControl.find( ".ui-guide" ),
622 volumeValue = volumeControl.find( ".ui-value" ),
623 volumeHandle = volumeControl.find( ".ui-handle" ),
624 handlerWidth = volumeHandle.width(),
625 handlerHeight = volumeHandle.height(),
626 volumeGuideHeight = volumeGuide.height(),
627 volumeGuideWidth = volumeGuide.width(),
631 handlerOffset = null,
632 volume = view[0].volume;
634 volumeGuideTop = parseInt( volumeGuide.offset().top, 10 );
635 volumeGuideLeft = parseInt( volumeGuide.offset().left, 10 );
636 volumeBase = volumeGuideLeft;
637 handlerOffset = volumeHandle.offset();
638 handlerOffset.top = volumeGuideTop - parseInt( ( handlerHeight - volumeGuideHeight ) / 2, 10 );
639 handlerOffset.left = volumeBase + parseInt( volumeGuideWidth * volume, 10 ) - parseInt( handlerWidth / 2, 10 );
640 volumeHandle.offset( handlerOffset );
641 volumeValue.offset( volumeGuide.offset() ).width( parseInt( volumeGuideWidth * ( volume ), 10 ) );
644 _setVolume: function ( value ) {
645 var viewElement = this.element[0];
647 if ( value < 0.0 || value > 1.0 ) {
651 viewElement.volume = value;
654 width: function ( value ) {
655 if ( this.options.fullScreen ) {
659 var view = this.element,
660 wrap = view.parent( ".ui-multimediaview-wrap" );
662 if ( arguments.length === 0 ) {
666 if ( !this._isVideo ) {
674 height: function ( value ) {
675 if ( !this._isVideo || this.options.fullScreen ) {
679 var view = this.element;
681 if ( arguments.length === 0 ) {
682 return view.height();
685 view.height( value );
689 fullScreen: function ( value ) {
690 if ( !this._isVideo ) {
694 var view = this.element,
695 option = this.options;
697 if ( arguments.length === 0 ) {
698 return option.fullScreen;
701 view.parents( ".ui-scrollview-clip" ).scrollview( "scrollTo", 0, 0 );
703 this.options.fullScreen = value;
708 refresh: function () {
713 $( document ).bind( "pagecreate create", function ( e ) {
714 $.tizen.multimediaview.prototype.enhanceWithin( e.target );
715 }).bind( "pagechange", function ( e ) {
716 $( e.target ).find( ".ui-multimediaview" ).each( function () {
717 var view = $( this ),
718 viewElement = view[0];
720 if ( viewElement.autoplay ) {
723 view.multimediaview( "refresh" );
725 }).bind( "pagebeforechange", function ( e ) {
726 $( e.target ).find( ".ui-multimediaview" ).each( function () {
727 var view = $( this ),
728 viewElement = view[0],
729 isFullscreen = view.multimediaview( "fullScreen" );
731 if ( isFullscreen ) {
732 view.multimediaview( "fullScreen", !isFullscreen );
735 if ( viewElement.played.length !== 0 ) {
741 $( window ).bind( "resize orientationchange", function ( e ) {
742 $( ".ui-page-active" ).find( ".ui-multimediaview" ).multimediaview( "refresh" );
745 } ( jQuery, document, window ) );