Initial commit of the Multimediaplayer app 97/17397/2 accepted/tizen/ivi/20140307.032032 submit/tizen/20140307.000420 submit/tizen/20140307.000925
authorbrianjjones <brian.j.jones@intel.com>
Thu, 6 Mar 2014 23:26:00 +0000 (15:26 -0800)
committerbrianjjones <brian.j.jones@intel.com>
Thu, 6 Mar 2014 23:54:05 +0000 (15:54 -0800)
Change-Id: I61526aec80bd0fb5d77cec6e138140d024e41268

22 files changed:
Makefile [new file with mode: 0644]
components/infoPanel/infoPanel.js [new file with mode: 0644]
components/spectrumAnalyzer/spectrumAnalyzer.js [new file with mode: 0644]
components/timeProgressBar/timeProgressBar.js [new file with mode: 0644]
config.xml [new file with mode: 0644]
css/music_library.css [new file with mode: 0644]
css/style.css [new file with mode: 0644]
icon.png [new file with mode: 0644]
images/audio-placeholder.jpg [new file with mode: 0644]
images/container-placeholder.jpg [new file with mode: 0644]
images/default-placeholder.jpg [new file with mode: 0644]
images/video-placeholder.jpg [new file with mode: 0644]
index.html [new file with mode: 0644]
js/carousel.js [new file with mode: 0644]
js/localcontent.js [new file with mode: 0644]
js/main.js [new file with mode: 0644]
js/mediacontent.js [new file with mode: 0644]
js/multimedialibrary.js [new file with mode: 0644]
js/remotecontent.js [new file with mode: 0644]
js/utils.js [new file with mode: 0644]
packaging/html5-ui-multimediaplayer.changes [new file with mode: 0644]
packaging/html5-ui-multimediaplayer.spec [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..dfbd526
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,20 @@
+PROJECT = html5UIMultimediaplayer
+
+VERSION := 0.0.1
+PACKAGE = $(PROJECT)-$(VERSION)
+
+INSTALL_FILES = $(PROJECT).wgt
+INSTALL_DIR = ${DESTDIR}/opt/usr/apps/.preinstallWidgets
+
+wgtPkg:
+       cp -r ${DESTDIR}/opt/usr/apps/_common/js/services js/
+       cp -r ${DESTDIR}/opt/usr/apps/_common/css/* css/
+       zip -r $(PROJECT).wgt components config.xml css icon.png images index.html js
+
+install:
+       @echo "Installing Multimediaplayer, stand by..."
+       mkdir -p $(INSTALL_DIR)/
+       cp $(PROJECT).wgt $(INSTALL_DIR)/
+
+dist:
+       tar czf ../$(PACKAGE).tar.bz2 .
diff --git a/components/infoPanel/infoPanel.js b/components/infoPanel/infoPanel.js
new file mode 100644 (file)
index 0000000..6d8c45a
--- /dev/null
@@ -0,0 +1,52 @@
+//audio info panel JQuery Plugin
+
+/**
+ * @module MultimediaPlayerApplication
+ */
+(function ($) {
+       "use strict";
+       /**
+        * Class which provides methods to fill content of info panel for JQuery plugin.
+        * @class InfoPanelObj
+        * @static
+        */
+       var InfoPanelObj = {
+                       title: 'NOW PLAYING',
+                       artist: 'ARTIST',
+                       album: 'ALBUM',
+                       name: 'SONG NAME',
+                       /**
+                        * Method is initializing info panel.
+                        * @method show
+                        * @param obj {Object} Object which contains properties title, artist, album and name.
+                        */
+                       show: function (obj) {
+                               InfoPanelObj.title = obj.title;
+                               InfoPanelObj.artist = obj.artist;
+                               InfoPanelObj.album = obj.album;
+                               InfoPanelObj.name = obj.name;
+                               this.empty();
+                               this.append('<div class="nowPlaying"></div><div class="fontColorNormal fontSizeLarger fontWeightBold artistNameTextMargin oneLineEllipsis">' + obj.artist.toUpperCase() + '</div>' +
+                                               '<div class="fontSizeSmaller fontColorTheme fontWeightBold oneLineEllipsis">' + obj.album.toUpperCase() + '</div>' +
+                                               '<div class="fontSizeXXLarge fontColorNormal fontWeightBold songNameTextPosition oneLineEllipsis">' + obj.name.toUpperCase() + '</div>');
+                               $(".nowPlaying").boxCaptionPlugin('init', obj.title.toUpperCase());
+                       }
+               };
+       /**
+        * Class which provides acces to InfoPanelObj methods.
+        * @class infoPanel
+        * @constructor
+        * @param method {Object} Identificator (name) of method.
+        * @return Result of called method.
+        */
+       $.fn.infoPanel = function (method) {
+               // Method calling logic
+               if (InfoPanelObj[method]) {
+                       return InfoPanelObj[method].apply(this, Array.prototype.slice.call(arguments, 1));
+               } else if (typeof method === 'object' || !method) {
+                       return InfoPanelObj.init.apply(this, arguments);
+               } else {
+                       $.error('Method ' +  method + ' does not exist on jQuery.infoPanelAPI');
+               }
+       };
+}(jQuery));
diff --git a/components/spectrumAnalyzer/spectrumAnalyzer.js b/components/spectrumAnalyzer/spectrumAnalyzer.js
new file mode 100644 (file)
index 0000000..3131bd0
--- /dev/null
@@ -0,0 +1,253 @@
+/**
+ * @module MultimediaPlayerApplication
+ */
+(function ($) {
+       "use strict";
+       /**
+        * Class which provides methods to fill content of spectrum analyzer for JQuery plugin.
+        * @class SpectrumAnalyzerObj
+        * @static
+        */
+       var SpectrumAnalyzerObj = {
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar1 {Integer}
+                        */
+                       bar1 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar2 {Integer}
+                        */
+                       bar2 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar3 {Integer}
+                        */
+                       bar3 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar4 {Integer}
+                        */
+                       bar4 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar5 {Integer}
+                        */
+                       bar5 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar6 {Integer}
+                        */
+                       bar6 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar7 {Integer}
+                        */
+                       bar7 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar8 {Integer}
+                        */
+                       bar8 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar9 {Integer}
+                        */
+                       bar9 : 0,
+                       /**
+                        * Holds value of spectrum analyzer bar.
+                        * @property bar10 {Integer}
+                        */
+                       bar10 : 0,
+                       /**
+                        * Method provides randomization for spectrum analyzer bars.
+                        * @method spectrumAnalyzerRandomize
+                        */
+                       spectrumAnalyzerRandomize : function () {
+                               SpectrumAnalyzerObj.bar1 = Math.floor((Math.random() *  6) + 1);
+                               SpectrumAnalyzerObj.bar2 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar3 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar4 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar5 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar6 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar7 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar8 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar9 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.bar10 = Math.floor((Math.random() * 6) + 1);
+                               SpectrumAnalyzerObj.showSpectrumAnalyzer(this);
+                       },
+                       /**
+                        * Method provides randomization for spectrum analyzer bars.
+                        * @method showSpectrumAnalyzer
+                        * @param thisObj {Object} Object which contains current object of this JQuery plugin.
+                        */
+                       showSpectrumAnalyzer : function (thisObj) {
+                               var bar1Count, bar2Count, bar3Count, bar4Count, bar5Count, bar6Count, bar7Count, bar8Count, bar9Count, bar10Count, bottom, i;
+                               bar1Count = 2 * SpectrumAnalyzerObj.bar1;
+                               bar2Count = 2 * SpectrumAnalyzerObj.bar2;
+                               bar3Count = 2 * SpectrumAnalyzerObj.bar3;
+                               bar4Count = 2 * SpectrumAnalyzerObj.bar4;
+                               bar5Count = 2 * SpectrumAnalyzerObj.bar5;
+                               bar6Count = 2 * SpectrumAnalyzerObj.bar6;
+                               bar7Count = 2 * SpectrumAnalyzerObj.bar7;
+                               bar8Count = 2 * SpectrumAnalyzerObj.bar8;
+                               bar9Count = 2 * SpectrumAnalyzerObj.bar9;
+                               bar10Count = 2 * SpectrumAnalyzerObj.bar10;
+                               if (bar1Count  > 12) {
+                                       bar1Count = 12;
+                               }
+                               if (bar2Count  > 12) {
+                                       bar2Count = 12;
+                               }
+                               if (bar3Count  > 12) {
+                                       bar3Count = 12;
+                               }
+                               if (bar4Count  > 12) {
+                                       bar4Count = 12;
+                               }
+                               if (bar5Count  > 12) {
+                                       bar5Count = 12;
+                               }
+                               if (bar6Count  > 12) {
+                                       bar6Count = 12;
+                               }
+                               if (bar7Count  > 12) {
+                                       bar7Count = 12;
+                               }
+                               if (bar8Count  > 12) {
+                                       bar8Count = 12;
+                               }
+                               if (bar9Count  > 12) {
+                                       bar9Count = 12;
+                               }
+                               if (bar10Count  > 12) {
+                                       bar10Count = 12;
+                               }
+                               thisObj.empty();
+                               bottom = 0;
+                               for (i = 0; i < bar1Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar1" class="bar1Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar1" class="bar1Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar2Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar2" class="bar2Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar2" class="bar2Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar3Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar3" class="bar3Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar3" class="bar3Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar4Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar4" class="bar4Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar4" class="bar4Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar5Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar5" class="bar5Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar5" class="bar5Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar6Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar6" class="bar6Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar6" class="bar6Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar7Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar7" class="bar7Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar7" class="bar7Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar8Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar8" class="bar8Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar8" class="bar8Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar9Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar9" class="bar9Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar9" class="bar9Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                               bottom = 0;
+                               for (i = 0; i < bar10Count; i++) {
+                                       bottom = bottom + 5;
+                                       if ((i % 2) === 0) {
+                                               thisObj.append('<div id="bar10" class="bar10Class barAnalyzer" style="bottom:' + bottom + 'px;"></div>');
+                                       } else {
+                                               thisObj.append('<div id="bar10" class="bar10Class barAnalyzer bgColorTheme boxShadow1" style="bottom:' + bottom + 'px;"></div>');
+                                       }
+                               }
+                       },
+                       /**
+                        * Method provides clear for spectrum analyzer bars.
+                        * @method clearSpectrumAnalyzer
+                        */
+                       clearSpectrumAnalyzer : function () {
+                               SpectrumAnalyzerObj.bar1 = 0;
+                               SpectrumAnalyzerObj.bar2 = 0;
+                               SpectrumAnalyzerObj.bar3 = 0;
+                               SpectrumAnalyzerObj.bar4 = 0;
+                               SpectrumAnalyzerObj.bar5 = 0;
+                               SpectrumAnalyzerObj.bar6 = 0;
+                               SpectrumAnalyzerObj.bar7 = 0;
+                               SpectrumAnalyzerObj.bar8 = 0;
+                               SpectrumAnalyzerObj.bar9 = 0;
+                               SpectrumAnalyzerObj.bar10 = 0;
+                               SpectrumAnalyzerObj.showSpectrumAnalyzer(this);
+                       }
+               };
+       /**
+        * Class which provides acces to SpectrumAnalyzerObj methods.
+        * @class spectrumAnalyzer
+        * @constructor
+        * @param method {Object} Identificator (name) of method.
+        * @return Result of called method.
+        */
+       $.fn.spectrumAnalyzer = function (method) {
+               // Method calling logic
+               if (SpectrumAnalyzerObj[method]) {
+                       return SpectrumAnalyzerObj[method].apply(this, Array.prototype.slice.call(arguments, 1));
+               } else if (typeof method === 'object' || !method) {
+                       return SpectrumAnalyzerObj.init.apply(this, arguments);
+               } else {
+                       $.error('Method ' +  method + ' does not exist on jQuery.spectrumAnalyzerAPI');
+               }
+       };
+}(jQuery));
diff --git a/components/timeProgressBar/timeProgressBar.js b/components/timeProgressBar/timeProgressBar.js
new file mode 100644 (file)
index 0000000..01cf3f2
--- /dev/null
@@ -0,0 +1,119 @@
+//audio time progress bar JQuery Plugin
+/**
+ * @module MultimediaPlayerApplication
+ */
+(function ($) {
+       "use strict";
+       /**
+        * Class which provides methods to fill content of time progress bar for JQuery plugin.
+        * @class TimeProgressBarObj
+        * @static
+        */
+       var TimeProgressBarObj = {
+                       /**
+                        * Holds current object of this JQuery plugin.
+                        * @property thisObj {Object}
+                        */
+                       thisObj: null,
+                       /**
+                        * Holds current object of position indicator.
+                        * @property positionIndicator {Object}
+                        */
+                       positionIndicator: null,
+                       /**
+                        * Holds current text of top right caption.
+                        * @property rightText {String}
+                        */
+                       rightText: null,
+                       /**
+                        * Holds current text of top left caption.
+                        * @property leftText {String}
+                        */
+                       leftText: null,
+                       /**
+                        * Holds current count of songs in playlist.
+                        * @property count {Integer}
+                        */
+                       count: 0,
+                       /**
+                        * Holds current index of song from playlist.
+                        * @property index {Integer}
+                        */
+                       index: 0,
+                       /**
+                        * Holds current estimation of song from playlist.
+                        * @property estimation {String}
+                        */
+                       estimation: "",
+                       /**
+                        * Holds current position of song from playlist.
+                        * @property position {Integer}
+                        */
+                       position: 0,
+                       /**
+                        * Method is initializing time progress bar.
+                        * @method init
+                        */
+                       init: function () {
+                               this.empty();
+                               this.append('<div id="songIndex" class="leftText fontColorNormal fontSizeLarge fontWeightBold">' +
+                                               '0/0' +
+                                               '</div>' +
+                                               '<div id="songTime" class="rightText fontColorTheme fontSizeLarge fontWeightBold">' +
+                                               '-0:00' +
+                                               '</div>' +
+                                               '<div id="songProgress" class="progressBar borderColorTheme">' +
+                                               '<div id="songSeek"     class="progressPot bgColorTheme boxShadow3"></div>' +
+                                       '</div>');
+
+                               TimeProgressBarObj.positionIndicator = $('#songProgress #songSeek');
+                               TimeProgressBarObj.leftText = $('#songIndex');
+                               TimeProgressBarObj.rightText = $('#songTime');
+                               TimeProgressBarObj.positionIndicator.css({width: '0 %'});
+                               TimeProgressBarObj.thisObj = this;
+
+                               $("#songProgress").click(function (e) {
+                                       var elWidth = $(this).width(),
+                                               parentOffset = $(this).parent().offset(),
+                                               relativeXPosition = (e.pageX - parentOffset.left), //offset -> method allows you to retrieve the current position of an element 'relative' to the document
+                                               progress = 0.00;
+                                       if (elWidth > 0) {
+                                               progress = relativeXPosition / elWidth;
+                                       }
+                                       TimeProgressBarObj.thisObj.trigger('positionChanged', {position: (progress * 100)});
+                               });
+                       },
+                       /**
+                        * Method is rendering position bar and position information from song.
+                        * @method show
+                        */
+                       show: function (objSong) {
+                               $("#songIndex").empty();
+                               $("#songIndex").append(objSong.index + '/' + objSong.count);
+                               $("#songTime").empty();
+                               $("#songTime").append(objSong.estimation);
+                               TimeProgressBarObj.count = objSong.count;
+                               TimeProgressBarObj.index = objSong.index;
+                               TimeProgressBarObj.estimation = objSong.estimation;
+                               TimeProgressBarObj.positionIndicator.css({width:  objSong.position + '%'});
+                               TimeProgressBarObj.position = objSong.position;
+                       }
+               };
+       /**
+        * Class which provides acces to TimeProgressBarObj methods.
+        * @class timeProgressBar
+        * @constructor
+        * @param method {Object} Identificator (name) of method.
+        * @return Result of called method.
+        */
+       $.fn.timeProgressBar = function (method) {
+               // Method calling logic
+               if (TimeProgressBarObj[method]) {
+                       return TimeProgressBarObj[method].apply(this, Array.prototype.slice.call(arguments, 1));
+               } else if (typeof method === 'object' || !method) {
+                       return TimeProgressBarObj.init.apply(this, arguments);
+               } else {
+                       $.error('Method ' +  method + ' does not exist on jQuery.infoPanelAPI');
+               }
+       };
+}(jQuery));
diff --git a/config.xml b/config.xml
new file mode 100644 (file)
index 0000000..502bf01
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
+       id="http://com.intel.tizen/multimediaplayer" version="1.0.0" viewmodes="fullscreen">
+       <access origin="*" subdomains="true" />
+       <tizen:application id="html5POC07.Multimediaplayer"
+               package="html5POC07" required_version="2.1" />
+       <content src="index.html" />
+       <icon src="icon.png" />
+       <name>Multimedia Player</name>
+       <tizen:privilege name="http://tizen.org/privilege/application.launch" />
+       <tizen:privilege name="http://tizen.org/privilege/filesystem.read" />
+       <tizen:privilege name="http://tizen.org/privilege/filesystem.write" />
+       <tizen:privilege name="http://tizen.org/privilege/fullscreen" />
+       <tizen:privilege name="http://tizen.org/privilege/content.read" />
+       <tizen:privilege name="http://tizen.org/privilege/speech" />
+       <tizen:privilege name="http://tizen.org/privilege/bluetooth.admin" />
+       <tizen:privilege name="http://tizen.org/privilege/bluetooth.spp" />
+       <tizen:privilege name="http://tizen.org/privilege/bluetooth.gap" />
+       <tizen:setting screen-orientation="portrait"
+               context-menu="enable" background-support="disable" encryption="disable"
+               install-location="auto" />
+</widget>
diff --git a/css/music_library.css b/css/music_library.css
new file mode 100644 (file)
index 0000000..66ef5fe
--- /dev/null
@@ -0,0 +1,87 @@
+/* ARTISTS */
+.musicLibraryContentList .musicElement {
+       padding: 10px 0 10px 0;
+       height: 70px;
+       box-shadow: none;
+       border-bottom-style: solid;
+       border-bottom-width: 2px;
+       line-height: 0;
+       text-align: left;
+       cursor: pointer;
+}
+
+.musicLibraryContentList .musicImage {
+       width: 70px;
+       height: 70px;
+       display: inline-block;
+       margin-right: 20px;
+       background-position: center center;
+       background-repeat: no-repeat;
+       background-size: 100% 100%;
+}
+
+.musicLibraryContentList .musicImage img {
+       width: 100%;
+       height: 100%;
+}
+
+.musicLibraryContentList .musicInfoBox {
+       display: inline-block;
+       vertical-align: top;
+       padding-top: 11px;
+       background-color: transparent !important;
+}
+
+.musicLibraryContentList .musicInfoBox.musicInfoBoxCentered {
+       padding-top: 20px;
+}
+
+.musicInfoBoxShort {
+       max-width: 470px;
+}
+
+.musicLibraryContentGrid {
+       line-height: 0;
+}
+
+.musicLibraryContentGrid .musicElement {
+       width: 180px;
+       height: 180px;
+       display: inline-block;
+       margin-right: 7px;
+       margin-bottom: 10px;
+       line-height: 0;
+       vertical-align: top;
+       cursor: pointer;
+}
+
+.musicLibraryContentGrid .musicImage {
+       width: 100%;
+       height: 100%;
+       background-position: center center;
+       background-repeat: no-repeat;
+       background-size: 100% 100%;
+}
+
+.musicLibraryContentGrid .musicImage img {
+       width: 100%;
+       height: 100%;
+}
+
+.musicLibraryContentGrid .musicInfoBox {
+       position: absolute;
+       top: 110px;
+       left: 0;
+       width: 180px;
+       height: 70px;
+       text-align: center;
+}
+
+.musicLibraryContentGrid .musicElement .contentTitle {
+       width: 160px;
+       margin: 10px auto 0 auto;
+}
+
+.textTransformUppercase {
+       text-transform: uppercase;
+}
\ No newline at end of file
diff --git a/css/style.css b/css/style.css
new file mode 100644 (file)
index 0000000..40aecb8
--- /dev/null
@@ -0,0 +1,359 @@
+.playerWrapper video {
+       position: absolute;
+       top: 230px;
+       left: 55px;
+       width: 610px;
+       height: 300px;
+       padding: 0;
+       margin: 0;
+       display: none;
+}
+
+.carouselWrapper {
+       width: 765px;
+       height: 300px;
+       position: absolute;
+       top: 230px;
+       left: -15px;
+       padding: 0;
+       margin: 0;
+       display: block;
+       border-bottom-style: solid;
+       border-bottom-width: 1px;
+}
+
+.carouselList {
+       margin: 0;
+       padding: 0;
+       list-style: none;
+       display: block;
+}
+
+.carouselList li {
+       display: block;
+       float: left;
+}
+
+.carouselItem {
+       margin-right: 15px;
+       display: block;
+       width: 240px;
+       height: 300px;
+       overflow: hidden;
+}
+
+.carouselItemSelected {
+       box-shadow: 0 0 5px 1px #1DA2FF;
+}
+
+.carouselImage {
+       width: 240px;
+       height: 240px;
+}
+
+.carouselImageReflect {
+       -webkit-box-reflect: below 1px
+               -webkit-gradient(linear, left top, left bottom, from(transparent),
+               to(rgba(250, 250, 225, 0.4) ) )
+}
+
+.albumCarouselDescription {
+       width: 230px;
+       height: 95px;
+       bottom: 95px;
+       padding: 0 5px;
+}
+
+.albumCarouselDescriptionText {
+       text-align: center;
+       top: 15px;
+}
+
+.backgroundAudioClass {
+       position: absolute;
+       top: 0;
+       left: 0;
+       width: 720px;
+       height: 1280px;
+       -webkit-background-size: cover;
+       -moz-background-size: cover;
+       -ms-background-size: cover;
+       -o-background-size: cover;
+       background-size: cover;
+}
+
+/* spectrum analyzer */
+.spectrumAnalyzer {
+       position: absolute;
+       top: 775px;
+       left: 300px;
+       width: 400px;
+       height: 110px;
+}
+
+.barAnalyzer {
+       position: absolute;
+       width: 33px;
+       height: 5px;
+       float: left;
+       margin-left: 5px;
+}
+
+.bar1Class {
+       left: 0;
+}
+
+.bar2Class {
+       left: 38px;
+}
+
+.bar3Class {
+       left: 76px;
+}
+
+.bar4Class {
+       left: 114px;
+}
+
+.bar5Class {
+       left: 152px;
+}
+
+.bar6Class {
+       left: 190px;
+}
+
+.bar7Class {
+       left: 228px;
+}
+
+.bar8Class {
+       left: 266px;
+}
+
+.bar9Class {
+       left: 304px;
+}
+
+.bar10Class {
+       left: 342px;
+}
+
+/* text panel artist, album, song title */
+.currentlyPlayingPreviewClass {
+       position: absolute;
+       top: 0;
+       left: 0;
+}
+
+.textPanel {
+       position: absolute;
+       width: 405px;
+       top: 577px;
+       left: 55px;
+}
+
+.blueIconText {
+       position: relative;
+       height: 25px;
+}
+
+/* time progress bar and remaining time */
+.timeBarClass {
+       position: absolute;
+       top: 0;
+       left: 283px;
+       width: 325px;
+       height: 80px;
+       font-size: large;
+}
+
+.infoPanelClass {
+       position: absolute;
+       left: 283px;
+       top: 80px;
+       width: 325px;
+}
+
+.volumeControlClass {
+       position: absolute;
+       top: 928px;
+       left: 35px;
+       width: 660px;
+       height: 55px;
+       background: none;
+}
+
+.progressPot {
+       position: absolute;
+       top: 4px;
+       left: 0;
+       width: 100%;
+       height: 3px;
+}
+
+.progressBar {
+       position: absolute;
+       top: 50px;
+       left: 0;
+       width: 325px;
+       height: 10px;
+       border-style: solid;
+       border-width: 0 0 1px 0;
+}
+
+.leftText {
+       position: absolute;
+       top: 10px;
+       left: 0;
+       width: 40px;
+       height: 15px;
+}
+
+.rightText {
+       position: absolute;
+       top: 10px;
+       right: 25px;
+       width: 40px;
+       height: 15px;
+}
+
+/*audio controls*/
+.audioControlsButtons {
+       position: absolute;
+       top: 1000px;
+       left: 0;
+       width: 610px;
+       padding: 0 55px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.previousBtn {
+       opacity: 1;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0;
+       margin-right: 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.previousBtnActive {
+       opacity: 1;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0;
+       margin-right: 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.prevBtnInactive {
+       opacity: 0.5;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0;
+       margin-right: 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.pauseBtn {
+       opacity: 1;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.playBtn {
+       opacity: 1;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.nextBtn {
+       opacity: 1;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.nextBtnActive {
+       opacity: 1;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.nextBtnInactive {
+       opacity: 0.5;
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.shuffleBtn {
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.shuffleBtnActive {
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.repeatBtn {
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0;
+       margin-left: 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.repeatBtnActive {
+       width: 66px;
+       height: 66px;
+       float: left;
+       margin: 0;
+       margin-left: 35px;
+       background-repeat: no-repeat;
+       background-position: 50%;
+}
+
+.songNameTextPosition {
+       top: 12px;
+}
+
+.artistNameTextMargin {
+       margin-bottom: 5px;
+}
\ No newline at end of file
diff --git a/icon.png b/icon.png
new file mode 100644 (file)
index 0000000..527249f
Binary files /dev/null and b/icon.png differ
diff --git a/images/audio-placeholder.jpg b/images/audio-placeholder.jpg
new file mode 100644 (file)
index 0000000..6a0ff2d
Binary files /dev/null and b/images/audio-placeholder.jpg differ
diff --git a/images/container-placeholder.jpg b/images/container-placeholder.jpg
new file mode 100644 (file)
index 0000000..f7d6068
Binary files /dev/null and b/images/container-placeholder.jpg differ
diff --git a/images/default-placeholder.jpg b/images/default-placeholder.jpg
new file mode 100644 (file)
index 0000000..bd2ce02
Binary files /dev/null and b/images/default-placeholder.jpg differ
diff --git a/images/video-placeholder.jpg b/images/video-placeholder.jpg
new file mode 100644 (file)
index 0000000..4f73bf2
Binary files /dev/null and b/images/video-placeholder.jpg differ
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..912d8c0
--- /dev/null
@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=720, height=1280, user-scalable=no" />
+<meta http-equiv="cache-control" content="no-cache" />
+<title>Multimedia Player</title>
+
+<!--  jquery and plugins  -->
+<script type="text/javascript"
+       src="./css/car/components/jQuery/jquery-1.8.2.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/knockout/knockout.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/carousel/jquery.carouFredSel-6.2.1-packed.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/carousel/jquery.touchSwipe.min.js"></script>
+
+<!-- jsRender Library Import -->
+<script type="text/javascript"
+       src="./css/car/components/jsViews/jsrender.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/jsViews/template.js"></script>
+
+<!-- User and car theme and javascripts  -->
+<link rel="stylesheet" href="./css/car/car.css" />
+<link rel="stylesheet"
+       href="./css/car/components/incomingCall/incomingCall.css" />
+<script type="text/javascript" src="./css/car/car.js"></script>
+<script type="text/javascript" src='./js/services/bootstrap.js'></script>
+
+<link rel="stylesheet" href="./css/style.css" />
+<link rel="stylesheet" href="./css/music_library.css" />
+
+<!-- Music Player -->
+<script type="text/javascript" src="./js/utils.js"></script>
+<script type="text/javascript" src="./js/carousel.js"></script>
+<script type="text/javascript" src="./js/localcontent.js"></script>
+<script type="text/javascript" src="./js/mediacontent.js"></script>
+<script type="text/javascript" src="./js/remotecontent.js"></script>
+<script type="text/javascript" src="./js/multimedialibrary.js"></script>
+<script type="text/javascript" src="./js/main.js"></script>
+
+<!-- Components -->
+<script type="text/javascript"
+       src="./css/car/components/audioPlayer/audioPlayer.js"></script>
+<script type="text/javascript" src="./components/infoPanel/infoPanel.js"></script>
+<script type="text/javascript"
+       src="./components/timeProgressBar/timeProgressBar.js"></script>
+<script type="text/javascript"
+       src="./components/spectrumAnalyzer/spectrumAnalyzer.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/configuration/configuration.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/bottomPanel/bottomPanel.js"></script>
+<script type="text/javascript"
+       src="./css/car/components/buttonControls/buttonControls.js"></script>
+<script type='text/javascript'
+       src='./css/car/components/progressBar/progressBar.js'></script>
+<link rel="stylesheet"
+       href="./css/car/components/progressBar/progressBar.css" />
+<script type='text/javascript'
+       src='./css/car/components/topBarIcons/topBarIcons.js'></script>
+<link rel="stylesheet"
+       href="./css/car/components/topBarIcons/topBarIcons.css" />
+<script type='text/javascript'
+       src='./css/car/components/boxCaption/boxCaption.js'></script>
+<link rel="stylesheet"
+       href="./css/car/components/boxCaption/boxCaption.css" />
+<script type='text/javascript'
+       src='./css/car/components/dateTime/dateTime.js'></script>
+<link rel="stylesheet" href="./css/car/components/dateTime/dateTime.css" />
+<script type="text/javascript"
+       src='./css/car/components/alphabetBookmark/alphabetBookmark.js'></script>
+<link rel="stylesheet"
+       href="./css/car/components/alphabetBookmark/alphabetBookmark.css" />
+<script type="text/javascript"
+       src='./css/car/components/library/library.js'></script>
+<link rel="stylesheet" href="./css/car/components/library/library.css" />
+
+<link rel="stylesheet"
+       href="./css/car/components/progressBar/volumeSlider.css" />
+<script type="text/javascript"
+       src="./css/car/components/jQuery/jquery.nouisliderix.js"></script>
+
+</head>
+
+<body>
+       <div id="topBarIcons"></div>
+
+       <div id="backgroundAudio"
+               class="backgroundAudioClass defaultPageBackgroundColor">
+               <div id="clockElement"></div>
+
+               <div id="libraryButton"
+                       class="button libraryButton fontSizeSmaller fontWeightBold fontColorNormal">LIBRARY</div>
+
+               <div id="multimediaPlayer" class="playerWrapper">
+                       <div id="carouselWrapper" class="carouselWrapper borderColorDark">
+                               <ul id="carouselList" class="carouselList"></ul>
+                       </div>
+
+                       <audio id="audioPlayer" src=""></audio>
+
+                       <video id="videoPlayer" src=""></video>
+
+                       <div id="volumeControl" class="volumeControlClass">
+                               <div id="VCicon"></div>
+                               <div class="sliderStart"></div>
+                               <div id="VCline" class="bgColorTheme"></div>
+                               <div id="VCinner" class="bgColorTheme boxShadow3"></div>
+                               <div class="noVolumeSlider"></div>
+                       </div>
+
+                       <div id="controlButtons" class="audioControlsButtons"></div>
+
+                       <div id="infoPanelWrapper" class="textPanel">
+                               <img id="thumbnail"
+                                       class="currentlyPlayingPreviewClass albumThumbnail carouselImage boxShadow2" />
+                               <div id="timeBar" class="timeBarClass"></div>
+                               <div id="infoPanel" class="infoPanelClass"></div>
+                       </div>
+
+                       <div id="spectAnalyzer" class="spectrumAnalyzer"></div>
+               </div>
+
+               <div id="bottomPanel" class="bottomPanel bottomPanelImg"></div>
+       </div>
+
+       <div id="musicLibrary" class="library pageBgColorNormalTransparent"></div>
+
+       <script type="text/html" id="localContentCategoryItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectLocalContent'>
+                       <div class="musicInfoBox musicInfoBoxCentered textBgColorNormalTransparent">
+                               <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                       </div>
+               </div>
+       </script>
+
+       <script type="text/html" id="localContentSubCategoryItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectLocalContent'>
+                       <div class="musicInfoBox musicInfoBoxCentered textBgColorNormalTransparent">
+                               <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                       </div>
+               </div>
+       </script>
+
+       <script type="text/html" id="localContentArtistItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectLocalContent'>
+                       <div class="musicInfoBox textBgColorNormalTransparent">
+                               <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                               <div class="contentSubtitle fontSizeXSmall fontWeightBold fontColorTheme oneLineEllipsis textTransformUppercase" data-bind="text: subtitle"></div>
+                       </div>
+               </div>
+       </script>
+
+       <script type="text/html" id="localContentAlbumItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectLocalContent'>
+                       <div class="musicImage albumThumbnail">
+                               <img data-bind="attr:{src: thumbnail}" />
+                       </div>
+                       <div class="musicInfoBox musicInfoBoxShort textBgColorNormalTransparent">
+                               <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                               <div class="contentSubtitle fontSizeXSmall fontWeightBold fontColorTheme oneLineEllipsis textTransformUppercase" data-bind="text: subtitle"></div>
+                       </div>
+               </div>
+       </script>
+
+       <script type="text/html" id="localContentAudioVideoItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectLocalMediaContent'>
+                       <div class="musicImage albumThumbnail">
+                               <img data-bind="attr:{src: Utils.getThumbnailPath($data)}" />
+                       </div>
+                       <div class="musicInfoBox musicInfoBoxShort textBgColorNormalTransparent">
+                               <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                               <div class="contentSubtitle fontSizeXSmall fontWeightBold fontColorTheme oneLineEllipsis textTransformUppercase" data-bind="text: Utils.getArtistName($data)"></div>
+                       </div>
+               </div>
+       </script>
+
+       <script type="text/html" id="mediaSourceItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectRemoteMediaSource'>
+                       <div class="musicInfoBox musicInfoBoxCentered textBgColorNormalTransparent">
+                               <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: friendlyName"></div>
+                       </div>
+               </div>
+       </script>
+
+       <script type="text/html" id="mediaContentItemTemplate">
+               <div class="musicElement boxShadow4 borderColorTheme" data-bind='click: MultimediaLibrary.selectRemoteContent'>
+                       <div class="musicImage albumThumbnail">
+                               <img data-bind="attr:{src: Utils.getThumbnailPath($data)}" />
+                       </div>
+                       <div class="musicInfoBox musicInfoBoxShort textBgColorNormalTransparent" data-bind="css: { musicInfoBoxCentered: type == 'CONTAINER' }">
+                               <!-- ko if: type == "CONTAINER" -->
+                                       <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                               <!-- /ko -->
+                               <!-- ko if: type != "CONTAINER" -->
+                                       <div class="contentTitle fontSizeLarge fontWeightBold fontColorNormal oneLineEllipsis textTransformUppercase" data-bind="text: title"></div>
+                                       <div class="contentSubtitle fontSizeXSmall fontWeightBold fontColorTheme oneLineEllipsis textTransformUppercase" data-bind="text: Utils.getArtistName($data)"></div>
+                               <!-- /ko -->
+                       </div>
+               </div>
+       </script>
+</body>
+</html>
diff --git a/js/carousel.js b/js/carousel.js
new file mode 100644 (file)
index 0000000..915f515
--- /dev/null
@@ -0,0 +1,221 @@
+/*global Utils */
+
+/**
+ * @module MultimediaPlayerApplication
+ */
+
+/**
+ * This class provides basic methods to operate with media content carousel (carouFredSel) like load and fill carousel with supplied audio content, scroll carousel to a given position, get current carousel position.
+ * Media content carousel represents a playlist of audio tracks and allows user to browse tracks by swiping to left (next track) or right (previous track). Each carousel's item contains thumbnail, artist name and title.
+ *
+ * @class Carousel
+ * @constructor
+ */
+var Carousel = function() {
+       "use strict";
+       this.initializeSwipe();
+};
+/**
+* This property holds audio media content array that carousel is filled with.
+* @property callHistory {Array}
+* @default []
+*/
+Carousel.prototype.allMediaContent = [];
+/**
+* This property holds carouFredSel object for internal use in carousel.
+* @property swipe {Object}
+* @private
+*/
+Carousel.prototype.swipe = null;
+/**
+* This property holds callback function which is called after current element/position in carousel is changed.
+* @property indexChangeCallback {Object}
+* @private
+*/
+Carousel.prototype.indexChangeCallback = null;
+/**
+ * This method adds listener that will be called right after the carousel finished scrolling and current element/position is changed.
+ *
+ * @method addIndexChangeListener
+ * @param indexChangeCallback {function()} Callback function to be invoked when current element/position of carousel is changed.
+ */
+Carousel.prototype.addIndexChangeListener = function(indexChangeCallback) {
+       "use strict";
+
+       this.indexChangeCallback = indexChangeCallback;
+};
+
+/**
+ * Initializes and configures carouFredSel carousel object.
+ *
+ * @method initializeSwipe
+ * @private
+ */
+ Carousel.prototype.initializeSwipe = function() {
+       "use strict";
+
+       var self = this;
+       if (!this.swipe) {
+               this.swipe = $('#carouselList').carouFredSel({
+                       auto : false,
+                       circular : false,
+                       infinite : false,
+                       width : 765,
+                       items : {
+                               visible : 3
+                       },
+                       swipe : {
+                               items : 1,
+                               duration : 150,
+                               onMouse : true,
+                               onTouch : true
+                       },
+                       scroll : {
+                               items : 1,
+                               duration : 150,
+                               onAfter : function(data) {
+                                       if (!!self.indexChangeCallback) {
+                                               self.indexChangeCallback(self.getCurrentPosition());
+                                       }
+                               }
+                       }
+               });
+               if (!this.swipe.length) {
+                       this.swipe = null;
+               }
+       }
+};
+
+/**
+ * Gets the position of selected carousel item.
+ *
+ * @method getCurrentPosition
+ */
+Carousel.prototype.getCurrentPosition = function() {
+       "use strict";
+       var self = this;
+       if (!!self.swipe) {
+               var pos = parseInt(self.swipe.triggerHandler("currentPosition"), 10);
+               return pos;
+       }
+       return null;
+};
+
+/**
+ * Scrolls the carousel to given index.
+ *
+ * @method slideTo
+ * @param index {Integer} New position to be scrolled to.
+ */
+Carousel.prototype.slideTo = function(index) {
+       "use strict";
+       if (!!this.swipe && index >= 0 && index < this.allMediaContent.length) {
+               this.swipe.trigger("slideTo", index);
+       }
+};
+/**
+ * This method fills carousel with audio media content and scrolls immediately the carousel to the designated position.
+ *
+ * @method loadMediaContent
+ * @param  allMediaContent {Array} Audio media content array to be filled in the carousel.
+ * @param  index {Integer} Position to be scrolled to.
+ */
+Carousel.prototype.loadMediaContent = function(allMediaContent, index) {
+       "use strict";
+       this.removeAllItems();
+       this.allMediaContent = allMediaContent;
+       this.insertPagesToSwipe();
+       if (index >= 0 && index < this.allMediaContent.length && !!this.swipe) {
+               this.swipe.trigger("slideTo", [ index, 0, {
+                       duration : 0
+               } ]);
+       }
+};
+
+/**
+ * Creates an HTML snippet representing one carousel item to be inserted into the carousel.
+ *
+ * @method createSwipeItem
+ * @param  mediaItem {Object} Object representing audio media item's information.
+ * @param  index {Integer} Position of item in carousel used to identify supplied audio media item's HTML DOM representation.
+ * @return {Object} jQuery DOM object representation of audio media item.
+ * @private
+ */
+Carousel.prototype.createSwipeItem = function(mediaItem, index) {
+       "use strict";
+
+       if (!!mediaItem) {
+               var carouselItem;
+               var thumbnail = Utils.getThumbnailPath(mediaItem);
+               var artist = Utils.getArtistName(mediaItem);
+               var album = Utils.getAlbumName(mediaItem);
+               var title = Utils.getMediaItemTitle(mediaItem);
+
+               carouselItem = '<li><div id="item_' + index + '" class="carouselItem">';
+               carouselItem += '<img class="carouselImage albumThumbnail carouselImageReflect" src="' + thumbnail + '" alt="">';
+               carouselItem += '<div class="albumCarouselDescription">';
+               carouselItem += '<div class="albumCarouselDescriptionText oneLineEllipsis fontColorNormal fontSizeLarge fontWeightBold">' + artist + '</div>';
+               carouselItem += '<div class="albumCarouselDescriptionText twoLinesEllipsis fontColorDimmedLight fontSizeXSmall fontWeightBold">' + title + '</div>';
+               carouselItem += '</div>';
+               carouselItem += '</div></li>';
+
+               carouselItem = $(carouselItem);
+               return carouselItem;
+       }
+
+       return null;
+};
+
+/**
+ * Inserts new carousel item into carousel.
+ *
+ * @method insertPagesToSwipe
+ * @private
+ */
+Carousel.prototype.insertPagesToSwipe = function() {
+       "use strict";
+       var self = this;
+       var carouselItem;
+       var clickHandler = function() {
+               self.swipe.trigger("slideTo", [ $(this), -1 ]);
+       };
+
+       for ( var index = this.allMediaContent.length - 1; index >= 0; --index) {
+               carouselItem = this.createSwipeItem(this.allMediaContent[index], index);
+               if (!!carouselItem && !!this.swipe) {
+                       this.swipe.trigger("insertItem", [ carouselItem, 0 ]);
+                       carouselItem.click(clickHandler);
+               }
+       }
+       this.addCarouselEdges();
+};
+
+/**
+ * Removes all items from the carousel.
+ *
+ * @method removeAllItems
+ */
+Carousel.prototype.removeAllItems = function() {
+       "use strict";
+       var carouselItem;
+
+       if (!!this.swipe) {
+               for ( var index = this.allMediaContent.length + 1; index >= 0; --index) {
+                       this.swipe.trigger("removeItem", index);
+               }
+       }
+};
+
+/**
+ * Adds emty carousel items to the beginning and the end of the carousel
+ * (to make sure first and last visible items appear in the middle of screen instead of at the edges when swiped to edges of carousel).
+ * @method addCarouselEdges
+ */
+Carousel.prototype.addCarouselEdges = function() {
+       "use strict";
+       if (!!this.swipe) {
+               var html = "<li><div class='carouselItem'></div></li>";
+               this.swipe.trigger("insertItem", [ html, 0 ]);
+               this.swipe.trigger("insertItem", [ html, "end", true ]);
+       }
+};
diff --git a/js/localcontent.js b/js/localcontent.js
new file mode 100644 (file)
index 0000000..9bf8028
--- /dev/null
@@ -0,0 +1,590 @@
+/*global ko */
+
+/**
+ * @module MultimediaPlayerApplication
+ */
+
+/**
+ * Class representing local media content for MultiMedia Player.
+ *
+ * @class LocalContent
+ * @constructor
+ */
+var LocalContent = function() {
+       "use strict";
+       var self = this;
+       this.content = tizen.content;
+       this.localContent = ko.observableArray([]);
+       this.history = ko.observableArray([]);
+
+       this.audioType = "AUDIO";
+       this.allAudioContent = ko.observableArray([]);
+       this.findAllAudioContent();
+
+       this.videoType = "VIDEO";
+       this.allVideoContent = ko.observableArray([]);
+       this.findAllVideoContent();
+
+       this.alphabetFilter = ko.observable("");
+
+       this.localContentComputed = ko.computed(function() {
+               if (self.alphabetFilter() !== "") {
+                       return ko.utils.arrayFilter(self.localContent(), function(content) {
+                               return content.title.toString().toLowerCase().trim().indexOf(self.alphabetFilter().toString().toLowerCase().trim()) === 0;
+                       });
+               }
+               return self.localContent();
+       });
+};
+
+/**
+ * This method fills local content with media categories.
+ *
+ * @method fillCategories
+ */
+LocalContent.prototype.fillCategories = function() {
+       "use strict";
+       var self = this;
+       var resultCategories = self.getCategories();
+       self.localContent(resultCategories);
+};
+
+/**
+ * This method provides media categories content.
+ *
+ * @method getCategories
+ * @return {Array} categories array
+ */
+LocalContent.prototype.getCategories = function() {
+       "use strict";
+       var self = this;
+       var categories = [];
+       categories.push({
+               title : "MUSIC",
+               subtitle : "",
+               operation : "browse_category",
+               type : self.audioType
+       });
+       categories.push({
+               title : "VIDEOS",
+               subtitle : "",
+               operation : "browse_category",
+               type : self.videoType
+       });
+       console.log(categories);
+       return categories;
+};
+
+/**
+ * This method fills local content array with media sub categories based on the content type.
+ *
+ * @method getCategories
+ */
+LocalContent.prototype.fillSubCategories = function(content) {
+       "use strict";
+       var self = this, resultCategories = [];
+       switch (content.type) {
+       case self.audioType:
+               resultCategories = self.getAudioSubCategories(content.type);
+               break;
+       case self.videoType:
+               resultCategories = self.getVideoSubCategories(content.type);
+               break;
+       default:
+               console.log("Type not supported");
+               break;
+       }
+       self.localContent(resultCategories);
+};
+
+/**
+ * This method provides audio categories content by type.
+ *
+ * @method getAudioSubCategories
+ * @param type {Object} media content type
+ * @return {Array} result categories array
+ */
+LocalContent.prototype.getAudioSubCategories = function(type) {
+       "use strict";
+       var self = this;
+       var categories = [ "ARTISTS", "ALBUMS", "ALL" ];
+       var resultCategories = [];
+       for ( var i = 0; i < categories.length; ++i) {
+               resultCategories.push({
+                       title : categories[i],
+                       subtitle : "",
+                       operation : "browse_" + categories[i].toLowerCase(),
+                       type : type
+               });
+       }
+       console.log(resultCategories);
+       return resultCategories;
+};
+
+/**
+ * This method provides video categories content by type.
+ *
+ * @method getVideoSubCategories
+ * @param type {Object} media content type
+ * @return {Array} result categories array
+ */
+LocalContent.prototype.getVideoSubCategories = function(type) {
+       "use strict";
+       var self = this;
+       var categories = [ "ARTISTS", "ALBUMS", "ALL" ];
+       var resultCategories = [];
+       for ( var i = 0; i < categories.length; ++i) {
+               resultCategories.push({
+                       title : categories[i],
+                       subtitle : "",
+                       operation : "browse_" + categories[i].toLowerCase(),
+                       type : type
+               });
+       }
+       console.log(resultCategories);
+       return resultCategories;
+};
+
+/**
+ * This method fills local content array with artists.
+ *
+ * @method fillArtists
+ * @param content {Object} media content
+ */
+LocalContent.prototype.fillArtists = function(content) {
+       "use strict";
+       var self = this;
+       var resultArtists = self.getAllArtists(content.type);
+       self.localContent(resultArtists);
+       self.localContent.sort(self.compareByTitle);
+};
+
+/**
+ * This method provides media content based on the type.
+ *
+ * @method getMediaContentByType
+ * @param type {String} media content type
+ * @return {Object} media content
+ */
+LocalContent.prototype.getMediaContentByType = function(type) {
+       "use strict";
+       var self = this, mediaContent = null;
+       switch (type) {
+       case self.audioType:
+               mediaContent = self.allAudioContent;
+               break;
+       case self.videoType:
+               mediaContent = self.allVideoContent;
+               break;
+       default:
+               console.log("Type not supported");
+               break;
+       }
+       return mediaContent.slice(0);
+};
+
+/**
+ * This method provides all artists content by type.
+ *
+ * @method getAllArtists
+ * @param type {String} media content type
+ * @return {Array} artists array
+ */
+LocalContent.prototype.getAllArtists = function(type) {
+       "use strict";
+       var self = this, resultArtists = [], mediaContent;
+       mediaContent = self.getMediaContentByType(type);
+       if (!!mediaContent && mediaContent.length) {
+               var artists = ko.utils.arrayMap(mediaContent, function(content) {
+                       if (!!content.artists && content.artists.length) {
+                               return content.artists.join(", ");
+                       }
+                       return "Unknown";
+               });
+
+               if (artists.length) {
+                       var uniqueArtists = ko.utils.arrayGetDistinctValues(artists);
+                       ko.utils.arrayForEach(uniqueArtists, function(artist) {
+                               var artistAlbumsAndContent = self.getArtistAlbumsAndContent(artist, type);
+                               resultArtists.push({
+                                       artist : artist,
+                                       type : type,
+                                       title : artist,
+                                       subtitle : artistAlbumsAndContent.albums.length + (artistAlbumsAndContent.albums.length === 1 ? " ALBUM, " : " ALBUMS, ") +
+                                                       artistAlbumsAndContent.content.length +
+                                                       (type === self.audioType ? (artistAlbumsAndContent.content.length === 1 ? " TRACK" : " TRACKS") :
+                                                       (artistAlbumsAndContent.content.length === 1 ? " MOVIE" : " MOVIES")),
+                                       operation : "browse_artist"
+                               });
+                       });
+               }
+       }
+       console.log(resultArtists);
+       return resultArtists;
+};
+
+/**
+ * This method provides albums and media content for a given artist and type of content.
+ *
+ * @method getArtistAlbumsAndContent
+ * @param artist {String} artist
+ * @param type {String} media content type
+ * @return {Object} a result object
+ */
+LocalContent.prototype.getArtistAlbumsAndContent = function(artist, type) {
+       "use strict";
+       var self = this;
+       var result = {
+               artist : artist,
+               albums : [],
+               content : []
+       };
+       var mediaContent = self.getMediaContentByType(type);
+       if (!!mediaContent && mediaContent.length && !!artist && artist !== "") {
+               var artistContent = ko.utils.arrayFilter(mediaContent, function(content) {
+                       /*global Utils */
+                       var artistName = Utils.getArtistName(content);
+                       return artistName === artist;
+               });
+               var artistAlbums = ko.utils.arrayMap(artistContent, function(content) {
+                       var artistAlbumName = Utils.getAlbumName(content);
+                       return artistAlbumName;
+               });
+               var uniqueArtistAlbums = ko.utils.arrayGetDistinctValues(artistAlbums);
+               if (!!uniqueArtistAlbums && uniqueArtistAlbums.length) {
+                       result.albums = uniqueArtistAlbums;
+               }
+               if (!!artistContent && artistContent.length) {
+                       result.content = artistContent;
+               }
+       }
+       console.log(result);
+       return result;
+};
+
+/**
+ * This method fills local content with albums for a given artist and type of content.
+ *
+ * @method fillArtistAlbums
+ * @param content {Object} media content info
+ */
+LocalContent.prototype.fillArtistAlbums = function(content) {
+       "use strict";
+       var self = this;
+       var artistAlbums = self.getArtistAlbums(content.artist, content.type);
+       self.localContent(artistAlbums);
+       self.localContent.sort(self.compareByTitle);
+};
+
+/**
+ * This method provides albums for a given artist and type of content.
+ *
+ * @method getArtistAlbums
+ * @param artist {String} artist
+ * @param type {String} media content type
+ * @return {Array} albums array
+ */
+LocalContent.prototype.getArtistAlbums = function(artist, type) {
+       "use strict";
+       var self = this, resultAlbums = [];
+       var artistAlbumsAndContent = self.getArtistAlbumsAndContent(artist, type);
+       ko.utils.arrayForEach(artistAlbumsAndContent.albums, function(album) {
+               var newAlbum = self.createAlbum(artist, album, type);
+               resultAlbums.push(newAlbum);
+       });
+       console.log(resultAlbums);
+       return resultAlbums;
+};
+
+/**
+ * This method creates a new album object for a given artist, album and type of content.
+ *
+ * @method getArtistAlbums
+ * @param artist {String} artist
+ * @param album {String} album title
+ * @param type {String} media content type
+ * @return {Object} album object
+ */
+LocalContent.prototype.createAlbum = function(artist, album, type) {
+       "use strict";
+       var self = this;
+       var newAlbum = {
+               artist : artist,
+               album : album,
+               title : album,
+               subtitle : artist,
+               thumbnail : self.getArtistAlbumThumbnail(artist, album, type),
+               operation : "browse_album",
+               type : type
+       };
+       return newAlbum;
+};
+
+/**
+ * This method fills local content with album for a given artist, album and type of content.
+ *
+ * @method fillArtistAlbumContent
+ * @param content {Object} media content
+ */
+LocalContent.prototype.fillArtistAlbumContent = function(content) {
+       "use strict";
+       var self = this;
+       var artistAlbumContent = self.getArtistAlbumContent(content.artist, content.album, content.type);
+       self.localContent(artistAlbumContent);
+       self.localContent.sort(self.compareByTitle);
+};
+
+/**
+ * This method provides album content for a given artist, album and type of content.
+ *
+ * @method getArtistAlbumContent
+ * @param artist {String} artist
+ * @param album {String} album title
+ * @param type {String} media content type
+ * @return {Object} content object
+ */
+LocalContent.prototype.getArtistAlbumContent = function(artist, album, type) {
+       "use strict";
+       var self = this, resultContent = [], mediaContent;
+       mediaContent = self.getMediaContentByType(type);
+       if (!!mediaContent && mediaContent.length) {
+               resultContent = ko.utils.arrayFilter(mediaContent, function(content) {
+                       var artistName = Utils.getArtistName(content);
+                       var artistAlbumName = Utils.getAlbumName(content);
+                       return artistName === artist && artistAlbumName === album;
+               });
+       }
+       console.log(resultContent);
+       return resultContent;
+};
+
+/**
+ * This method fills local content with albums for a given type of content.
+ *
+ * @method fillAlbums
+ * @param content {Object} media content
+ */
+LocalContent.prototype.fillAlbums = function(content) {
+       "use strict";
+       var self = this;
+       var allAlbums = self.getAllAlbums(content.type);
+       self.localContent(allAlbums);
+       self.localContent.sort(self.compareByTitle);
+};
+
+/**
+ * This method provides all albums for a given type of content.
+ *
+ * @method getAllAlbums
+ * @param type {String} media content type
+ * @return {Array} albums array
+ */
+LocalContent.prototype.getAllAlbums = function(type) {
+       "use strict";
+       var self = this, resultAlbums = [], mediaContent;
+       mediaContent = self.getMediaContentByType(type);
+       if (!!mediaContent && mediaContent.length) {
+               var albums = ko.utils.arrayMap(mediaContent, function(content) {
+                       var artistName = Utils.getArtistName(content);
+                       var artistAlbumName = Utils.getAlbumName(content);
+                       var album = {
+                               artist : artistName,
+                               album : artistAlbumName
+                       };
+                       return JSON.stringify(album);
+               });
+               if (!!albums && albums.length) {
+                       var uniqueAlbums = ko.utils.arrayGetDistinctValues(albums);
+                       ko.utils.arrayForEach(uniqueAlbums, function(albumJSON) {
+                               var album = JSON.parse(albumJSON);
+                               var newAlbum = self.createAlbum(album.artist, album.album, type);
+                               resultAlbums.push(newAlbum);
+                       });
+               }
+       }
+       console.log(resultAlbums);
+       return resultAlbums;
+};
+
+/**
+ * This method fills local content with albums for a given type of content.
+ *
+ * @method fillAlbums
+ * @param content {Object} media content
+ */
+LocalContent.prototype.fillAll = function(content) {
+       "use strict";
+       var self = this, mediaContent;
+       mediaContent = self.getMediaContentByType(content.type);
+       self.localContent(mediaContent);
+       self.localContent.sort(self.compareByTitle);
+};
+
+/**
+ * This method empties local content.
+ *
+ * @method clearLocalContent
+ */
+LocalContent.prototype.clearLocalContent = function() {
+       "use strict";
+       var self = this;
+       self.localContent.removeAll();
+       self.localContent([]);
+};
+
+/**
+ * This method empties history of opened local content.
+ *
+ * @method clearLocalContent
+ */
+LocalContent.prototype.clearHistory = function() {
+       "use strict";
+       var self = this;
+       self.history.removeAll();
+       self.history([]);
+};
+
+/**
+ * This method adds given local content to history of opened local content and clears local content.
+ *
+ * @method pushToHistory
+ * @param content {Object} media content
+ */
+LocalContent.prototype.pushToHistory = function(content) {
+       "use strict";
+       var self = this;
+       self.clearLocalContent();
+       self.history.push(content);
+};
+
+/**
+ * This method gets a thumbnail for a given artist, album and type of media content.
+ *
+ * @method getArtistAlbumThumbnail
+ * @param artist {String} artist
+ * @param album {String} album title
+ * @param type {String} media content type
+ */
+LocalContent.prototype.getArtistAlbumThumbnail = function(artist, album, type) {
+       "use strict";
+       var self = this;
+       var artistAlbumContent = this.getArtistAlbumContent(artist, album, type);
+       var contentWithThumbnail = ko.utils.arrayFirst(artistAlbumContent, function(content) {
+               return !!content.thumbnailURIs && content.thumbnailURIs.length;
+       });
+       return Utils.getThumbnailPath(contentWithThumbnail, type);
+};
+
+/**
+ * This method filters audio content out of all content.
+ *
+ * @method findAllAudioContent
+ */
+LocalContent.prototype.findAllAudioContent = function() {
+       "use strict";
+       var self = this;
+       if (!!self.content) {
+               var filter = new tizen.AttributeFilter("type", "EXACTLY", self.audioType);
+               self.content.find(function(content) {
+                       self.onContentArraySuccess(content, self.audioType);
+               }, function(error) {
+                       self.onError(error);
+               }, null, filter);
+       }
+};
+
+/**
+ * This method filters video content out of all content.
+ *
+ * @method findAllVideoContent
+ */
+LocalContent.prototype.findAllVideoContent = function() {
+       "use strict";
+       var self = this;
+       if (!!self.content) {
+               var filter = new tizen.AttributeFilter("type", "EXACTLY", self.videoType);
+               self.content.find(function(content) {
+                       self.onContentArraySuccess(content, self.videoType);
+               }, function(error) {
+                       self.onError(error);
+               }, null, filter);
+       }
+};
+
+/**
+ * This method is success callback for find content methods (findAllAudioContent and findAllVideoContent).
+ *
+ * @method onContentArraySuccess
+ * @param content {Object} media content
+ * @param type {String} content type
+ */
+LocalContent.prototype.onContentArraySuccess = function(content, type) {
+       "use strict";
+       var self = this;
+       console.log(content);
+
+       content.sort(self.compareByTitle);
+
+       switch (type) {
+       case self.audioType:
+               self.allAudioContent(content);
+               break;
+       case self.videoType:
+               self.allVideoContent(content);
+               break;
+       default:
+               break;
+       }
+};
+
+/**
+ * This method compares neighbouring content items by their titles for sorting.
+ *
+ * @method compareByTitle
+ * @param left {Object} media content
+ * @param right {Object} media content
+ */
+LocalContent.prototype.compareByTitle = function(left, right) {
+       "use strict";
+       var leftTitle = "Unknown";
+       if (!!left.title && left.title !== "") {
+               leftTitle = left.title;
+       }
+       leftTitle = leftTitle.toString().trim().toLowerCase();
+       var rightTitle = "Unknown";
+       if (!!right.title && right.title !== "") {
+               rightTitle = right.title;
+       }
+       rightTitle = rightTitle.toString().trim().toLowerCase();
+       return leftTitle === rightTitle ? 0 : (leftTitle < rightTitle) ? -1 : 1;
+};
+
+/**
+ * This method is error callback for find content methods (findAllAudioContent and findAllVideoContent).
+ *
+ * @method onError
+ * @param error {Object} returned error
+ */
+LocalContent.prototype.onError = function(error) {
+       "use strict";
+       var self = this;
+       console.log(error);
+};
+
+/**
+ * This method gets selected local content based on the content type.
+ *
+ * @method getSelectedLocalContent
+ * @param type {String} media content type
+ * @return {Object} mediaItem object
+ */
+LocalContent.prototype.getSelectedLocalContent = function(type) {
+       "use strict";
+       var self = this;
+       if (!!self.localContent() && self.localContent().length) {
+               return ko.utils.arrayFilter(self.localContent(), function(mediaItem) {
+                       return mediaItem.type === type;
+               });
+       }
+       return [];
+};
\ No newline at end of file
diff --git a/js/main.js b/js/main.js
new file mode 100644 (file)
index 0000000..b41b6ad
--- /dev/null
@@ -0,0 +1,112 @@
+/*global MultimediaLibrary, Utils*/
+
+/**
+ * Multimedia player application allows user to play back audio and video content from local sources via
+ * [tizen.content API](https://developer.tizen.org/dev-guide/2.2.1/org.tizen.web.device.apireference/tizen/content.html) and
+ * remote DLNA content via [IVI WRT MediaServer API](https://review.tizen.org/git/?p=profile/ivi/wrt-plugins-ivi.git;a=tree;f=src/MediaServer).
+ * Application uses HTML5 [audio](http://www.w3.org/wiki/HTML/Elements/audio) and [video](http://www.w3.org/wiki/HTML/Elements/video) elements
+ * to playback content from sources.
+ *
+ * Upper part of application contains a media content carousel (carouFredSel) in case audio content is selected or a rendered video (HTML5 video element).
+ *
+ * Lower part of application contains basic information about played audio/video content like thumbnail, title, artist name, album name, duration and controls like play, pause, next, previous, volume.
+ * Additionaly Multimedia player application can be controlled using speech recognition via {{#crossLink "Speech"}}{{/crossLink}} component.
+ *
+ * Audio/video content to be played can be selected from {{#crossLink "MultimediaLibrary"}}{{/crossLink}} component. Playback of audio and video content is controlled by {{#crossLink "AudioPlayer"}}{{/crossLink}} component.
+ *
+ * Hover and click on elements in images below to navigate to components of Multimedia Player application.
+ *
+ * <img id="Image-Maps_1201312180420487" src="../assets/img/music.png" usemap="#Image-Maps_1201312180420487" border="0" width="649" height="1152" alt="" />
+ *   <map id="_Image-Maps_1201312180420487" name="Image-Maps_1201312180420487">
+ *     <area shape="rect" coords="0,0,573,78" href="../classes/TopBarIcons.html" alt="top bar icons" title="Top bar icons" />
+ *     <area shape="rect" coords="0,77,644,132" href="../classes/Clock.html" alt="clock" title="Clock"    />
+ *     <area shape="rect" coords="0,994,644,1147" href="../classes/BottomPanel.html" alt="bottom panel" title="Bottom panel" />
+ *     <area shape="rect" coords="573,1,644,76" href="../modules/Settings.html" alt="Settings" title="Settings"    />
+ *     <area  shape="rect" coords="512,135,648,181" href="../classes/MultimediaLibrary.html" alt="Multimedia library" title="Multimedia library" />
+ *     <area  shape="rect" coords="0,206,648,476" href="../classes/Carousel.html" alt="Multimedia carousel" title="Multimedia Carousel" />
+ *     <area  shape="rect" coords="21,823,634,885" href="../classes/AudioPlayer.html#method_setVolumeControlSelector" alt="Volume control" title="Volume control" />
+ *     <area  shape="rect" coords="23,890,636,961" href="../classes/AudioPlayer.html#method_setControlButtonsSelector" alt="Control buttons" title="Control buttons" />
+ *     <area  shape="rect" coords="298,530,612,585" href="../classes/AudioPlayer.html#method_setTimeProgressBarSelector" alt="Time progress bar" title="Time progress bar" />
+ *     <area  shape="rect" coords="297,589,611,721" href="../classes/AudioPlayer.html#method_setInfoPanelSelector" alt="Info panel" title="Info panel" />
+ *     <area  shape="rect" coords="267,725,626,818" href="../classes/AudioPlayer.html#method_setSpectrumAnalyzerSelector" alt="Spectrum analyzer" title="Spectrum analyzer" />
+ *     <area  shape="rect" coords="45,517,267,738" href="../classes/AudioPlayer.html#method_setThumbnailSelector" alt="Thumbnail" title="Thumbnail" />
+ *   </map>
+ *
+ * @module MultimediaPlayerApplication
+ * @main MultimediaPlayerApplication
+ * @class MultimediaPlayer
+ */
+
+ /**
+ * Adds the listener object to receive notifications when the speech recognizer returns a speech command to control multimedia player: PLAY, STOP, NEXT, PREVIOUS.
+ *
+ * @method initVoiceRecognition
+ */
+var initVoiceRecognition = function() {
+    "use strict";
+    /* global Speech */
+    if (typeof (Speech) !== 'undefined') {
+        Speech.addVoiceRecognitionListener({
+            onplay : function() {
+                $('#multimediaPlayer').audioAPI('playPause', true);
+            },
+            onstop : function() {
+                $('#multimediaPlayer').audioAPI('playPause', false);
+            },
+            onnext : function() {
+                $('#multimediaPlayer').audioAPI('next');
+                $('#multimediaPlayer').audioAPI('playPause', true);
+            },
+            onprevious : function() {
+                $('#multimediaPlayer').audioAPI('previous');
+                $('#multimediaPlayer').audioAPI('playPause', true);
+            }
+        });
+    } else {
+        console.warn("Speech API is not available.");
+    }
+};
+
+/**
+ * Method which provides methods to initialize UI and create listener for
+ * changing themes of music player.
+ *
+ * @method init
+ * @constructor
+ */
+var bootstrap;
+var init = function() {
+    "use strict";
+    /*global Bootstrap */
+    bootstrap = new Bootstrap(function(status) {
+        $("#libraryButton").on("click", function() {
+            MultimediaLibrary.show();
+        });
+
+        $("#videoPlayer").on("click", function() {
+            Utils.launchFullScreen(this);
+        });
+
+        $("#topBarIcons").topBarIconsPlugin('init');
+        $("#clockElement").ClockPlugin('init', 5);
+        $("#clockElement").ClockPlugin('startTimer');
+        $('#bottomPanel').bottomPanel('init');
+
+        $('#multimediaPlayer').audioAPI('setControlButtonsSelector', '#controlButtons');
+        $('#multimediaPlayer').audioAPI('setTimeProgressBarSelector', '#timeBar');
+        $('#multimediaPlayer').audioAPI('setSpectrumAnalyzerSelector', '#spectAnalyzer');
+        $('#multimediaPlayer').audioAPI('setInfoPanelSelector', '#infoPanel');
+        $('#multimediaPlayer').audioAPI('setThumbnailSelector', '#thumbnail');
+        $('#multimediaPlayer').audioAPI('setVolumeControlSelector', '.noVolumeSlider');
+        $('#multimediaPlayer').audioAPI('init', [], "#audioPlayer", "#videoPlayer");
+
+        MultimediaLibrary.init();
+        initVoiceRecognition();
+    });
+};
+
+$(document).ready(function() {
+    "use strict";
+    // debug mode - window.setTimeout("init()", 20000);
+    init();
+});
diff --git a/js/mediacontent.js b/js/mediacontent.js
new file mode 100644 (file)
index 0000000..c76a35c
--- /dev/null
@@ -0,0 +1,225 @@
+/******************************************************************************
+ * Copyright 2012 Intel Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+/**
+ * @module MultimediaPlayerApplication
+ */
+
+/**
+ * Class providing objects mapping the org.gnome.UPnP MediaObject2 and MediaItem2 interfaces.
+ *
+ * @class mediacontent
+ */
+var mediacontent = window.mediacontent = {};
+
+/**
+ * Generic media object.
+ *
+ * @class MediaObject
+ * @return {Object} MediaObject objects
+ */
+mediacontent.MediaObject = function(proxy) {
+       "use strict";
+       this.proxy = proxy;
+       if (proxy) {
+               this.id = proxy.Path;
+               this.type = proxy.Type;
+               this.title = proxy.DisplayName;
+       }
+       return this;
+};
+
+/**
+ * Gets the MediaObject metadata info.
+ *
+ * @method getMetaData
+ * @return {Object} metadata object
+ */
+mediacontent.MediaObject.prototype.getMetaData = function() {
+       "use strict";
+       return this.proxy.callMethod("org.gnome.UPnP.MediaObject2", "GetMetaData", []);
+};
+
+/**
+ * Mediacontent object of type container.
+ *
+ * @class MediaContainer
+ * @param proxy {Object} media source object
+ * @return {Object} MediaContainer object
+ */
+mediacontent.MediaContainer = function(proxy) {
+       "use strict";
+       mediacontent.MediaObject.call(this, proxy);
+       this.type = "CONTAINER";
+       this.directoryURI = "";
+       this.storageType = "EXTERNAL";
+       return this;
+};
+
+mediacontent.MediaContainer.prototype = new mediacontent.MediaObject();
+mediacontent.MediaContainer.prototype.constructor = mediacontent.MediaContainer;
+
+/**
+ * Mediacontent object of type media item. Provides access to properties of media items.
+ *
+ * @class MediaItem
+ * @param proxy {Object} media source object
+ * @return {Object} MediaItem object
+ */
+mediacontent.MediaItem = function(proxy) {
+       "use strict";
+       mediacontent.MediaObject.call(this, proxy);
+       if (proxy) {
+               this.mimeType = proxy.MIMEType;
+               if (proxy.URLs) {
+                       this.contentURI = proxy.URLs[0];
+               } else {
+                       this.contentURI = "";
+               }
+               this.size = proxy.Size;
+               this.releaseDate = proxy.Date;
+               this.modifiedDate = null;
+               this.name = this.title;
+               this.editableAttributes = [];
+               this.thumbnailURIs = [];
+               if (!!proxy.AlbumArtURL && proxy.AlbumArtURL !== "") {
+                       this.thumbnailURIs.push(proxy.AlbumArtURL);
+               }
+               this.description = "Unknown";
+               this.rating = 0;
+       }
+       this.type = "OTHER";
+       return this;
+};
+
+mediacontent.MediaItem.prototype = new mediacontent.MediaObject();
+mediacontent.MediaItem.prototype.constructor = mediacontent.MediaItem;
+
+/**
+ * Mediacontent object of type video. Extends a basic media item object with video-specific attributes.
+ *
+ * @class MediaVideo
+ * @param proxy {Object} media source object
+ * @return {Object} MediaVideo object
+ */
+mediacontent.MediaVideo = function(proxy) {
+       "use strict";
+       mediacontent.MediaItem.call(this, proxy);
+       if (proxy) {
+               this.duration = proxy.Duration * 1000; //Tizen's ContentVideo is in ms
+               this.width = proxy.Width;
+               this.height = proxy.Height;
+               if (proxy.Album) {
+                       this.album = proxy.Album;
+               } else {
+                       this.album = "Unknown";
+               }
+               if (proxy.Artist) {
+                       this.artists = [ proxy.Artist ];
+               } else {
+                       this.artists = [ "Unknown" ];
+               }
+               this.geolocation = null;
+       }
+       this.type = "VIDEO";
+       return this;
+};
+
+mediacontent.MediaVideo.prototype = new mediacontent.MediaItem();
+mediacontent.MediaVideo.prototype.constructor = mediacontent.MediaVideo;
+
+/**
+ * Mediacontent object of type audio. Extends a basic media item object with audio-specific attributes.
+ *
+ * @class MediaAudio
+ * @param proxy {Object} media source object
+ * @return {Object} MediaAudio object
+ */
+mediacontent.MediaAudio = function(proxy) {
+       "use strict";
+       mediacontent.MediaItem.call(this, proxy);
+       if (proxy) {
+               this.bitrate = proxy.SampleRate;
+               this.duration = proxy.Duration * 1000; //Tizen's ContentAudio is in ms
+               if (proxy.Album) {
+                       this.album = proxy.Album;
+               } else {
+                       this.album = "Unknown";
+               }
+               //this;
+               if (proxy.Artist) {
+                       this.artists = [ proxy.Artist ];
+               } else {
+                       this.artists = [ "Unknown" ];
+               }
+               this.genres = [];
+               this.composers = [ "Unknown" ];
+               this.lyrics = null;
+               this.copyright = "Unknown";
+               this.trackNumber = 0;
+       }
+       this.type = "AUDIO";
+       return this;
+};
+
+mediacontent.MediaAudio.prototype = new mediacontent.MediaItem();
+mediacontent.MediaAudio.prototype.constructor = mediacontent.MediaAudio;
+
+/**
+ * Mediacontent object of type image. Extends a basic media item object with image-specific attributes.
+ *
+ * @class MediaImage
+ * @param proxy {Object} media source object
+ * @return {Object} MediaImage object
+ */
+mediacontent.MediaImage = function(proxy) {
+       "use strict";
+       mediacontent.MediaItem.call(this, proxy);
+       if (proxy) {
+               this.width = proxy.Width;
+               this.height = proxy.Height;
+               this.orientation = "NORMAL";
+       }
+       this.type = "IMAGE";
+       return this;
+};
+
+mediacontent.MediaImage.prototype = new mediacontent.MediaItem();
+mediacontent.MediaImage.prototype.constructor = mediacontent.MediaImage;
+
+/**
+ * Returns appropriate media object based on the given parameter media type.
+ *
+ * @class mediaObjectForProps
+ * @param props {Object} media source object
+ * @return {Object} correct media object by props.type
+ */
+mediacontent.mediaObjectForProps = function(props) {
+       "use strict";
+       if (props.Type.indexOf("container") === 0 || props.Type.indexOf("album") === 0 || props.Type.indexOf("person") === 0 || props.Type.indexOf("genre") === 0){
+               return new mediacontent.MediaContainer(props);
+       }
+       if (props.Type.indexOf("video") === 0){
+               return new mediacontent.MediaVideo(props);
+       }
+       if (props.Type.indexOf("audio") === 0 || props.Type.indexOf("music") === 0){
+               return new mediacontent.MediaAudio(props);
+       }
+       if (props.Type.indexOf("image") === 0){
+               return new mediacontent.MediaImage(props);
+       }
+       return new mediacontent.MediaItem(props);
+};
diff --git a/js/multimedialibrary.js b/js/multimedialibrary.js
new file mode 100644 (file)
index 0000000..4336428
--- /dev/null
@@ -0,0 +1,484 @@
+/**
+ * @module MultimediaPlayerApplication
+ */
+
+/**
+ * Class which provides methods to operate with Multimedia player library that utilizes {{#crossLink "Library"}}{{/crossLink}} component.
+ * Library allows user to select either LOCAL or REMOTE content.
+ * LOCAL content tab is devided by content type into 2 categories: MUSIC and VIDEOS. Each content type can be browsed by ARTISTS, ALBUMS or ALL (all audio tracks / all video files). Moreover ARTISTS can be browsed by ALBUMS.
+ * Following hierarchical list represents structure of LOCAL content:
+ *
+ * * LOCAL
+ *  * MUSIC
+ *      * ARTISTS
+ *          * ALBUMS
+ *      * ALBUMS
+ *      * ALL
+ *  * VIDEOS
+ *      * ARTISTS
+ *          * ALBUMS
+ *      * ALBUMS
+ *      * ALL
+ *
+ * REMOTE content tab contains alphabetically sorted list of available DLNA media servers and integrates browsing of DLNA Media Container of selected DLNA media server
+ * starting from root element and drill down through folders (keeps the structure as is defined by remote server).
+ *
+ * Clicking audio track in library selects all audio items from the current list, creates and sets audio player playlist, shows and fills in the media carousel and starts playing selected track.
+ * Clicking video file in library selects all video items from the current list, creates a sets video player playlist, shows video element and starts playing selected video. Clicking on the rendered video or back button lets the user toggle between windowed and fullscreen presentation of the video.
+ *
+ * @class MultimediaLibrary
+ * @static
+ */
+var MultimediaLibrary = {
+       remoteContent : null,
+       remoteContentReScanInterval : null,
+       localContent : null,
+       carousel : null,
+       speechObj : null,
+       /*global ko */
+       mediaContentTemplate : ko.observable(""),
+       /**
+        * Holds status of music library initialization.
+        *
+        * @property initialized {Boolean}
+        */
+       initialized : false,
+       /**
+        * Method is initializing music library.
+        *
+        * @method init
+        */
+       init : function() {
+               "use strict";
+               /*global RemoteContent*/
+               MultimediaLibrary.remoteContent = new RemoteContent();
+               MultimediaLibrary.remoteContent.setMediaSourceLostListener(function(mediaSourceId) {
+                       if ($("#mediaContentList").length) {
+                               if (!!MultimediaLibrary.remoteContent.selectedMediaSource() && MultimediaLibrary.remoteContent.selectedMediaSource().id === mediaSourceId) {
+                                       MultimediaLibrary.showMediaSources();
+                               }
+                       }
+               });
+               /*global LocalContent*/
+               MultimediaLibrary.localContent = new LocalContent();
+               /*global Carousel*/
+               MultimediaLibrary.carousel = new Carousel();
+               MultimediaLibrary.carousel.addIndexChangeListener(function(index) {
+                       console.log("NEW CAROUSEL INDEX " + index);
+                       if ($('#multimediaPlayer').audioAPI('getCurrentPlayerType') === MultimediaLibrary.localContent.audioType) {
+                               $('#multimediaPlayer').audioAPI('play', index);
+                       }
+               });
+               $('#multimediaPlayer').audioAPI('addIndexChangeListener', function(index) {
+                       console.log("NEW PLAYER INDEX " + index);
+                       if ($('#multimediaPlayer').audioAPI('getCurrentPlayerType') === MultimediaLibrary.localContent.audioType) {
+                               MultimediaLibrary.carousel.slideTo(index);
+                       }
+               });
+
+               $('#musicLibrary').library("setSectionTitle", "MULTIMEDIA LIBRARY");
+               $('#musicLibrary').library("init");
+
+               var tabMenuItems = [ {
+                       text : "LOCAL",
+                       selected : true
+               }, {
+                       text : "REMOTE",
+                       selected : false
+               } ];
+
+               var tabMenuModel = {
+                       Tabs : tabMenuItems
+               };
+               $('#library').library("setAlphabetVisible", true);
+               $('#musicLibrary').library("tabMenuTemplateCompile", tabMenuModel);
+               $('#musicLibrary').bind('eventClick_GridViewBtn', function() {
+                       $('#musicLibrary').library('changeContentClass', "musicLibraryContentGrid");
+               });
+               $('#musicLibrary').bind('eventClick_ListViewBtn', function() {
+                       $('#musicLibrary').library('changeContentClass', "musicLibraryContentList");
+               });
+               $('#musicLibrary').bind('eventClick_SearchViewBtn', function() {
+                       // search code here
+               });
+               $('#musicLibrary').bind('eventClick_menuItemBtn', function(e, data) {
+                       MultimediaLibrary.renderTabContent(data.Index);
+               });
+               $('#musicLibrary').bind('eventClick_closeSubpanel', function() {
+               });
+               $("#alphabetBookmarkList").on("letterClick", function(event, letter) {
+                       console.log(letter);
+                       MultimediaLibrary.remoteContent.alphabetFilter(letter === "*" ? "" : letter);
+                       MultimediaLibrary.localContent.alphabetFilter(letter === "*" ? "" : letter);
+               });
+               MultimediaLibrary.renderTabContent($('#musicLibrary').library('getSelectetTopTabIndex'));
+               MultimediaLibrary.initialized = true;
+       },
+       /**
+        * Shows music library panel.
+        *
+        * @method show
+        */
+       show : function() {
+               "use strict";
+               $('#musicLibrary').library("showPage");
+       },
+       /**
+        * Hides music library panel.
+        *
+        * @method hide
+        */
+       hide : function() {
+               "use strict";
+               $('#musicLibrary').library("hidePage");
+       },
+       /**
+        * Renders the Multimedia library content for given library tab.
+        *
+        * @method renderTabContent
+        */
+       renderTabContent : function(tabIndex) {
+               "use strict";
+               switch (tabIndex) {
+               case 0:
+                       MultimediaLibrary.showLocalContent();
+                       break;
+               case 1:
+                       MultimediaLibrary.showMediaSources();
+                       break;
+               default:
+                       break;
+               }
+       },
+       /**
+        * Shows local content categories [Music, Videos] in grid or list view.
+        *
+        * @method showLocalContent
+        */
+       showLocalContent : function() {
+               "use strict";
+               var view = "";
+               switch ($('#musicLibrary').library('getSelectetLeftTabIndex')) {
+               /*global GRID_TAB, LIST_TAB*/
+               case GRID_TAB:
+                       view = "musicLibraryContentGrid";
+                       break;
+               case LIST_TAB:
+                       view = "musicLibraryContentList";
+                       break;
+               default:
+                       view = "musicLibraryContentList";
+                       break;
+               }
+               MultimediaLibrary.localContent.clearLocalContent();
+               MultimediaLibrary.localContent.clearHistory();
+               $('#musicLibrary').library('closeSubpanel');
+               $('#musicLibrary').library("clearContent");
+               $('#musicLibrary').library("changeContentClass", view);
+               MultimediaLibrary.mediaContentTemplate("localContentCategoryItemTemplate");
+               var localContentElement = '<div data-bind="template: { name: function() { return MultimediaLibrary.mediaContentTemplate(); }, foreach: MultimediaLibrary.localContent.localContentComputed }"></div>';
+               $(localContentElement).appendTo($('.' + view));
+               ko.applyBindings(MultimediaLibrary.localContent);
+               MultimediaLibrary.localContent.fillCategories();
+       },
+       /**
+        * Shows available media sources/servers in grid or list view.
+        *
+        * @method showMediaSources
+        */
+       showMediaSources : function() {
+               "use strict";
+               var view = "";
+               switch ($('#musicLibrary').library('getSelectetLeftTabIndex')) {
+               case GRID_TAB:
+                       view = "musicLibraryContentGrid";
+                       break;
+               case LIST_TAB:
+                       view = "musicLibraryContentList";
+                       break;
+               default:
+                       view = "musicLibraryContentList";
+                       break;
+               }
+               $('#musicLibrary').library('closeSubpanel');
+               $('#musicLibrary').library("clearContent");
+               $('#musicLibrary').library("changeContentClass", view);
+               var mediaSourcesElement = '<div id="remoteMediaServers" data-bind="template: { name: \'mediaSourceItemTemplate\', foreach: MultimediaLibrary.remoteContent.mediaSourcesComputed }"></div>';
+               $(mediaSourcesElement).appendTo($('.' + view));
+               ko.applyBindings(MultimediaLibrary.remoteContent);
+               MultimediaLibrary.remoteContent.selectedMediaSource(null);
+               MultimediaLibrary.remoteContent.resetMediaContainers();
+               MultimediaLibrary.remoteContent.resetMediaContainerItems();
+               MultimediaLibrary.remoteContent.scanMediaServerNetwork();
+               if (!MultimediaLibrary.remoteContentReScanInterval) {
+                       MultimediaLibrary.remoteContentReScanInterval = setInterval(function() {
+                               if (($("#mediaContentList").length || $("#remoteMediaServers").length) && $('#musicLibrary').library('isVisible')) {
+                                       MultimediaLibrary.remoteContent.scanMediaServerNetwork();
+                               }
+                       }, 5000);
+               }
+       },
+       /**
+        * Opens the supplied media source/server, shows the content of its root
+        * directory and navigation bar containing the name of selected server and
+        * back button to navigate back to list of available media sources.
+        *
+        * @method selectRemoteMediaSource {Object} Representation of media
+        *         source/server to be opened.
+        * @param mediaSource {}
+        */
+       selectRemoteMediaSource : function(mediaSource) {
+               "use strict";
+               if (!!mediaSource) {
+                       MultimediaLibrary.remoteContent.selectMediaSource(mediaSource);
+                       var subpanelModel = {
+                               action : function() {
+                                       MultimediaLibrary.goBackRemoteContent();
+                               },
+                               actionName : "BACK",
+                               textTitle : "SERVER",
+                               textSubtitle : mediaSource.friendlyName ? mediaSource.friendlyName.toUpperCase() : "-"
+                       };
+                       $('#musicLibrary').library("subpanelContentTemplateCompile", subpanelModel);
+               }
+
+               $('#musicLibrary').library("clearContent");
+               var viewType = "";
+               if ($('#musicLibrary').library('getSelectetLeftTabIndex') === GRID_TAB) {
+                       viewType = "musicLibraryContentGrid";
+               } else {
+                       viewType = "musicLibraryContentList";
+               }
+               $('#musicLibrary').library("changeContentClass", viewType);
+               var mediaContainerItemsElement = '<div id="mediaContentList" data-bind="template: { name: \'mediaContentItemTemplate\', foreach: MultimediaLibrary.remoteContent.mediaContainerItemsComputed }"></div>';
+               $(mediaContainerItemsElement).appendTo($('.' + viewType));
+               ko.applyBindings(MultimediaLibrary.remoteContent);
+       },
+       /**
+        * In case the supplied media object is type of container the method browses
+        * and shows the content of container, shows a navigation bar containing the
+        * title of selected container and back button to navigate back in hierarchy
+        * of opened containers. Otherwise it closes the library and starts playing
+        * video/audio or show the image.
+        *
+        * @method selectRemoteContent {}
+        * @param mediaItem {MediaObject} Media container or media item to be
+        *         opened.
+        */
+       selectRemoteContent : function(mediaItem) {
+               "use strict";
+               if (!!mediaItem) {
+                       MultimediaLibrary.remoteContent.selectMediaContainerItem(mediaItem);
+                       if (mediaItem.type === "CONTAINER") {
+                               var textTitle = "FOLDER";
+                               var mediaContainersLength = MultimediaLibrary.remoteContent.mediaContainers().length;
+                               if (mediaContainersLength) {
+                                       if (mediaContainersLength > 2) {
+                                               textTitle = MultimediaLibrary.remoteContent.mediaContainers()[mediaContainersLength - 2].title.toUpperCase();
+                                       } else {
+                                               textTitle = MultimediaLibrary.remoteContent.selectedMediaSource().friendlyName.toUpperCase();
+                                       }
+                               }
+                               var subpanelModel = {
+                                       action : function() {
+                                               MultimediaLibrary.goBackRemoteContent();
+                                       },
+                                       actionName : "BACK",
+                                       textTitle : textTitle,
+                                       textSubtitle : mediaItem.title ? mediaItem.title.toUpperCase() : "-"
+                               };
+                               $('#musicLibrary').library("subpanelContentTemplateCompile", subpanelModel);
+                       } else {
+                               $('#playerWrapper').audioAPI('playPause', false);
+                               var index;
+                               switch (mediaItem.type) {
+                               case MultimediaLibrary.localContent.audioType:
+                                       MultimediaLibrary.showAudio();
+                                       var audioContent = MultimediaLibrary.remoteContent.getAudioFromSelectedContainer();
+                                       index = audioContent.indexOf(mediaItem);
+                                       MultimediaLibrary.carousel.loadMediaContent(audioContent, index);
+                                       $('#multimediaPlayer').audioAPI('playAudioContent', audioContent, index, true, mediaItem.type);
+                                       MultimediaLibrary.hide();
+                                       break;
+                               case MultimediaLibrary.localContent.videoType:
+                                       MultimediaLibrary.showVideo();
+                                       var videoContent = MultimediaLibrary.remoteContent.getVideoFromSelectedContainer();
+                                       index = videoContent.indexOf(mediaItem);
+                                       $('#multimediaPlayer').audioAPI('playAudioContent', videoContent, index, true, mediaItem.type);
+                                       MultimediaLibrary.hide();
+                                       break;
+                               default:
+                                       console.log("Media type not supported!");
+                                       break;
+                               }
+                       }
+               }
+       },
+       /**
+        * Navigates user back in hierarchy of opened containers/servers.
+        *
+        * @method goBackRemoteContent
+        */
+       goBackRemoteContent : function() {
+               "use strict";
+               if (MultimediaLibrary.remoteContent.mediaContainers().length > 1) {
+                       MultimediaLibrary.remoteContent.mediaContainers.pop();
+                       var remoteContent = MultimediaLibrary.remoteContent.mediaContainers.pop();
+                       if (remoteContent.title.toString().toLowerCase().trim() !== "root") {
+                               MultimediaLibrary.selectRemoteContent(remoteContent);
+                       } else {
+                               MultimediaLibrary.selectRemoteMediaSource(MultimediaLibrary.remoteContent.selectedMediaSource());
+                       }
+               } else {
+                       MultimediaLibrary.showMediaSources();
+               }
+       },
+       /**
+        * Loads and displays selected local content based on the given content data.
+        *
+        * @method selectLocalMediaContent
+        * @param content {object} media content
+        */
+       selectLocalMediaContent : function(content) {
+               "use strict";
+               if (!!content) {
+                       var index;
+                       switch (content.type) {
+                       case MultimediaLibrary.localContent.audioType:
+                               MultimediaLibrary.showAudio();
+                               var audioContent = MultimediaLibrary.localContent.getSelectedLocalContent(content.type);
+                               index = audioContent.indexOf(content);
+                               MultimediaLibrary.carousel.loadMediaContent(audioContent, index);
+                               $('#multimediaPlayer').audioAPI('playAudioContent', audioContent, index, true, content.type);
+                               MultimediaLibrary.hide();
+                               break;
+                       case MultimediaLibrary.localContent.videoType:
+                               MultimediaLibrary.showVideo();
+                               var videoContent = MultimediaLibrary.localContent.getSelectedLocalContent(content.type);
+                               index = videoContent.indexOf(content);
+                               $('#multimediaPlayer').audioAPI('playAudioContent', videoContent, index, true, content.type);
+                               MultimediaLibrary.hide();
+                               break;
+                       default:
+                               console.log("Not supported type!");
+                               break;
+                       }
+               }
+       },
+       /**
+        * Displays audio player, hides video player.
+        *
+        * @method showAudio
+        */
+       showAudio : function() {
+               "use strict";
+               $("#videoPlayer").css({
+                       display : "none"
+               });
+               $("#audioPlayer").css({
+                       display : "block"
+               });
+               $("#carouselWrapper").css({
+                       display : "block"
+               });
+       },
+       /**
+        * Displays video player, hides audio player.
+        *
+        * @method showVideo
+        */
+       showVideo : function() {
+               "use strict";
+               $("#audioPlayer").css({
+                       display : "none"
+               });
+               $("#carouselWrapper").css({
+                       display : "none"
+               });
+               $("#videoPlayer").css({
+                       display : "block"
+               });
+       },
+
+       /**
+        * Sets the local content for MultiMedia library based on the given content.
+        *
+        * @method selectLocalContent
+        * @param content {Object} media content
+        */
+       selectLocalContent : function(content) {
+               "use strict";
+               console.log(content);
+               var self = this;
+
+               var subpanelModel;
+
+               if (MultimediaLibrary.localContent.history().length) {
+                       var title = MultimediaLibrary.localContent.history()[MultimediaLibrary.localContent.history().length - 1].title;
+                       subpanelModel = {
+                               action : function() {
+                                       MultimediaLibrary.goBackLocalContent();
+                               },
+                               actionName : "BACK",
+                               textTitle : title.toUpperCase(),
+                               textSubtitle : content.title ? content.title.toUpperCase() : "-"
+                       };
+               } else {
+                       subpanelModel = {
+                               action : function() {
+                                       MultimediaLibrary.goBackLocalContent();
+                               },
+                               actionName : "BACK",
+                               textTitle : "LOCAL",
+                               textSubtitle : content.title ? content.title.toUpperCase() : "-"
+                       };
+               }
+               $('#musicLibrary').library("subpanelContentTemplateCompile", subpanelModel);
+
+               MultimediaLibrary.localContent.pushToHistory(content);
+
+               switch (content.operation) {
+               case "browse_category":
+                       MultimediaLibrary.mediaContentTemplate("localContentSubCategoryItemTemplate");
+                       MultimediaLibrary.localContent.fillSubCategories(content);
+                       break;
+               case "browse_artists":
+                       MultimediaLibrary.mediaContentTemplate("localContentArtistItemTemplate");
+                       MultimediaLibrary.localContent.fillArtists(content);
+                       break;
+               case "browse_artist":
+                       MultimediaLibrary.mediaContentTemplate("localContentAlbumItemTemplate");
+                       MultimediaLibrary.localContent.fillArtistAlbums(content);
+                       break;
+               case "browse_album":
+                       MultimediaLibrary.mediaContentTemplate("localContentAudioVideoItemTemplate");
+                       MultimediaLibrary.localContent.fillArtistAlbumContent(content);
+                       break;
+               case "browse_albums":
+                       MultimediaLibrary.mediaContentTemplate("localContentAlbumItemTemplate");
+                       MultimediaLibrary.localContent.fillAlbums(content);
+                       break;
+               case "browse_all":
+                       MultimediaLibrary.mediaContentTemplate("localContentAudioVideoItemTemplate");
+                       MultimediaLibrary.localContent.fillAll(content);
+                       break;
+               default:
+                       break;
+               }
+       },
+       /**
+        * Navigates user back in history of opened local content.
+        *
+        * @method goBackLocalContent
+        */
+       goBackLocalContent : function() {
+               "use strict";
+               if (MultimediaLibrary.localContent.history().length > 1) {
+                       MultimediaLibrary.localContent.history.pop();
+                       var localContent = MultimediaLibrary.localContent.history.pop();
+                       MultimediaLibrary.selectLocalContent(localContent);
+               } else {
+                       MultimediaLibrary.showLocalContent();
+               }
+       }
+};
\ No newline at end of file
diff --git a/js/remotecontent.js b/js/remotecontent.js
new file mode 100644 (file)
index 0000000..86bbb84
--- /dev/null
@@ -0,0 +1,369 @@
+/* global ko */
+
+/**
+ * @module MultimediaPlayerApplication
+ */
+
+/**
+ * Class representing remote media content for MultiMedia Player.
+ *
+ * @class RemoteContent
+ * @constructor
+ */
+var RemoteContent = function() {
+       "use strict";
+       var self = this;
+       this.mediaServer = tizen.mediaserver;
+       this.mediaSources = ko.observableArray([]);
+       this.selectedMediaSource = ko.observable(null);
+
+       this.mediaContainers = ko.observableArray([]);
+       this.selectedMediaContainer = ko.observable(null);
+
+       this.mediaContainerItems = ko.observableArray([]);
+       this.selectedMediaContainerItem = ko.observable(null);
+
+       this.currentBrowseOperation = "";
+       this.alphabetFilter = ko.observable("");
+       this.onMediaSourceLost = null;
+
+       this.mediaSourcesComputed = ko.computed(function() {
+               if (self.alphabetFilter() !== "") {
+                       return ko.utils.arrayFilter(self.mediaSources(), function(mediaSource) {
+                               return mediaSource.friendlyName.toString().toLowerCase().trim().indexOf(self.alphabetFilter().toString().toLowerCase().trim()) === 0;
+                       });
+               }
+               return self.mediaSources();
+       });
+       this.mediaContainersComputed = ko.computed(function() {
+               if (self.alphabetFilter() !== "") {
+                       return ko.utils.arrayFilter(self.mediaContainers(), function(mediaContainer) {
+                               return mediaContainer.title.toString().toLowerCase().trim().indexOf(self.alphabetFilter().toString().toLowerCase().trim()) === 0;
+                       });
+               }
+               return self.mediaContainers();
+       });
+       this.mediaContainerItemsComputed = ko.computed(function() {
+               if (self.alphabetFilter() !== "") {
+                       return ko.utils.arrayFilter(self.mediaContainerItems(), function(mediaItem) {
+                               return mediaItem.title.toString().toLowerCase().trim().indexOf(self.alphabetFilter().toString().toLowerCase().trim()) === 0;
+                       });
+               }
+               return self.mediaContainerItems();
+       });
+};
+
+/**
+ * Scans network for available DLNA server and adds it to media sources.
+ *
+ * @method scanMediaServerNetwork
+ */
+RemoteContent.prototype.scanMediaServerNetwork = function() {
+       "use strict";
+       var self = this;
+       if (!!self.mediaServer) {
+               self.clearDisappearedMediaSources();
+               self.mediaServer.scanNetwork(function(source) {
+                       self.addMediaSource(source);
+               }, function(err) {
+                       console.log("An error has occured while scanning network: " + err.message);
+                       console.log(err);
+               });
+       }
+};
+
+/**
+ * Adds given media source to the list.
+ *
+ * @method addMediaSource
+ * @param source {Object} media source
+ */
+RemoteContent.prototype.addMediaSource = function(source) {
+       "use strict";
+       var self = this;
+       console.log(source);
+       if (!!source) {
+               if (!source.friendlyName) {
+                       return;
+               }
+               source.timestamp = new Date().getTime();
+               var sourceExists = false;
+               for ( var i = 0; i < self.mediaSources().length; ++i) {
+                       var src = self.mediaSources()[i];
+                       if (src.id === source.id) {
+                               self.mediaSources()[i] = source;
+                               sourceExists = true;
+                               break;
+                       }
+               }
+               if (!sourceExists) {
+                       self.mediaSources.push(source);
+               }
+
+               self.mediaSources.sort(function(left, right) {
+                       var leftFriendlyName = "Unknown";
+                       if (!!left.friendlyName && left.friendlyName !== "") {
+                               leftFriendlyName = left.friendlyName;
+                       }
+                       leftFriendlyName = leftFriendlyName.toString().trim().toLowerCase();
+                       var rightFriendlyName = "Unknown";
+                       if (!!right.friendlyName && right.friendlyName !== "") {
+                               rightFriendlyName = right.friendlyName;
+                       }
+                       rightFriendlyName = rightFriendlyName.toString().trim().toLowerCase();
+                       return leftFriendlyName === rightFriendlyName ? 0 : (leftFriendlyName < rightFriendlyName) ? -1 : 1;
+               });
+       }
+};
+
+/**
+ * Sets given media source as selected and adds new media container based on the media source and sets it as selected.
+ *
+ * @method selectMediaSource
+ * @param mediaSource {Object} media source
+ */
+RemoteContent.prototype.selectMediaSource = function(mediaSource) {
+       "use strict";
+       var self = this;
+       console.log(mediaSource);
+       self.selectedMediaSource(null);
+       if (!!mediaSource) {
+               self.selectedMediaSource(mediaSource);
+               self.resetMediaContainers();
+               self.resetMediaContainerItems();
+               var mediaSourceContainerProps = {
+                       DisplayName : mediaSource.root.title,
+                       Path : mediaSource.root.id,
+                       Type : mediaSource.root.type
+               };
+               /*global mediacontent*/
+               var mediaContainer = new mediacontent.MediaContainer(mediaSourceContainerProps);
+               self.mediaContainers.push(mediaContainer);
+               self.selectMediaContainer(mediaContainer);
+       }
+};
+
+/**
+ * Sets given media container as selected.
+ *
+ * @method selectMediaContainer
+ * @param mediaSourceContainer {Object} media source container
+ */
+RemoteContent.prototype.selectMediaContainer = function(mediaSourceContainer) {
+       "use strict";
+       var self = this;
+       console.log(mediaSourceContainer);
+       if (!!mediaSourceContainer) {
+               self.resetMediaContainerItems();
+
+               for ( var i = self.mediaContainers().length - 1; i >= 0; --i) {
+                       if (self.mediaContainers()[i] !== mediaSourceContainer) {
+                               self.mediaContainers.pop();
+                       } else {
+                               break;
+                       }
+               }
+               console.log(self.mediaContainers());
+               self.selectedMediaContainer(mediaSourceContainer);
+               self.browseMediaSourceContainer(self.selectedMediaSource(), mediaSourceContainer);
+       }
+};
+
+/**
+ * Sets given media container item as selected.
+ *
+ * @method selectMediaContainerItem
+ * @param mediaContainerItem {Object} media source container
+ */
+RemoteContent.prototype.selectMediaContainerItem = function(mediaContainerItem) {
+       "use strict";
+       var self = this;
+       console.log(mediaContainerItem);
+       if (!!mediaContainerItem) {
+               self.selectedMediaContainerItem(mediaContainerItem);
+               if (mediaContainerItem.type === "CONTAINER") {
+                       self.mediaContainers.push(mediaContainerItem);
+                       self.selectMediaContainer(mediaContainerItem);
+               }
+       }
+};
+
+/**
+ * Sets selected media source to null and empties media sources.
+ *
+ * @method resetMediaSource
+ */
+RemoteContent.prototype.resetMediaSource = function() {
+       "use strict";
+       var self = this;
+       self.selectedMediaSource(null);
+       self.mediaSources.removeAll();
+       self.mediaSources([]);
+};
+
+/**
+ * Removes expired media sources and invokes onMediaSourceLost listener.
+ *
+ * @method clearDisappearedMediaSources
+ */
+RemoteContent.prototype.clearDisappearedMediaSources = function() {
+       "use strict";
+       var self = this;
+       if (self.mediaSources().length) {
+               for ( var i = self.mediaSources().length - 1; i >= 0; --i) {
+                       if (new Date().getTime() - self.mediaSources()[i].timestamp > 10000) {
+                               var mediaSourceId = self.mediaSources()[i].id;
+                               self.mediaSources.remove(self.mediaSources()[i]);
+                               if (!!self.onMediaSourceLost) {
+                                       self.onMediaSourceLost(mediaSourceId);
+                               }
+                       }
+               }
+       }
+};
+
+/**
+ * Sets the listener to receive notifications when media source is lost.
+ *
+ * @method setMediaSourceLostListener
+ * @param onMediaSourceLost {Function(mediaSourceId)} Event listener to be set.
+ */
+RemoteContent.prototype.setMediaSourceLostListener = function(onMediaSourceLost) {
+       "use strict";
+       var self = this;
+       if (!!onMediaSourceLost) {
+               self.onMediaSourceLost = onMediaSourceLost;
+       }
+};
+
+/**
+ * Sets selected media container to null and empties media containers.
+ *
+ * @method resetMediaContainers
+ */
+RemoteContent.prototype.resetMediaContainers = function() {
+       "use strict";
+       var self = this;
+       self.selectedMediaContainer(null);
+       self.mediaContainers.removeAll();
+       self.mediaContainers([]);
+};
+
+/**
+ * Sets selected media container item to null and empties media container items.
+ *
+ * @method resetMediaContainerItems
+ */
+RemoteContent.prototype.resetMediaContainerItems = function() {
+       "use strict";
+       var self = this;
+       self.selectedMediaContainerItem(null);
+       self.mediaContainerItems.removeAll();
+       self.mediaContainerItems([]);
+};
+
+/**
+ * Gets media source by its id.
+ *
+ * @method getMediaSourceById
+ * @param id (String) media source id
+ */
+RemoteContent.prototype.getMediaSourceById = function(id) {
+       "use strict";
+       var self = this;
+       var mediaSource = ko.utils.arrayFirst(self.mediaSources(), function(ms) {
+               return ms.id === id;
+       });
+       return mediaSource;
+};
+
+/**
+ * Gets media container by its id.
+ *
+ * @method getMediaContainerById
+ * @param id {String} media container id
+ */
+RemoteContent.prototype.getMediaContainerById = function(id) {
+       "use strict";
+       var self = this;
+       var mediaContainer = ko.utils.arrayFirst(self.mediaContainers(), function(mc) {
+               return mc.id === id;
+       });
+       return mediaContainer;
+};
+
+/**
+ * Browses given media source container.
+ *
+ * @method browseMediaSourceContainer
+ * @param source {Object} media source
+ * @param container {Object} media container
+ */
+RemoteContent.prototype.browseMediaSourceContainer = function(source, container) {
+       "use strict";
+       var self = this;
+       var browseCount = 100;
+       var browseOffset = 0;
+       var localOp = "Browse_" + source.id + "_" + container.id;
+
+       function browseErrorCB(str) {
+               console.log("Error browsing " + container.id + " : " + str);
+       }
+
+       function browseContainerCB(jsonArray) {
+               console.log(jsonArray);
+               if (self.currentBrowseOperation !== localOp) {
+                       return;
+               }
+               for ( var i = 0; i < jsonArray.length; ++i) {
+                       self.mediaContainerItems.push(mediacontent.mediaObjectForProps(jsonArray[i]));
+               }
+
+               if (jsonArray.length === browseCount) {
+                       browseOffset += browseCount;
+                       source.browse(container.id, "+DisplayName", browseCount, browseOffset, browseContainerCB, browseErrorCB);
+               } else {
+                       self.currentBrowseOperation = "";
+               }
+       }
+
+       if (self.currentBrowseOperation === localOp) {
+               return;
+       }
+
+       self.currentBrowseOperation = localOp;
+
+       source.browse(container.id, "+DisplayName", browseCount, browseOffset, browseContainerCB, browseErrorCB);
+};
+
+/**
+ * Gets audio media items from selected container.
+ *
+ * @method getAudioFromSelectedContainer
+ */
+RemoteContent.prototype.getAudioFromSelectedContainer = function() {
+       "use strict";
+       var self = this;
+       if (!!self.mediaContainerItemsComputed() && self.mediaContainerItemsComputed().length) {
+               return ko.utils.arrayFilter(self.mediaContainerItemsComputed(), function(mediaItem) {
+                       return mediaItem.type === "AUDIO";
+               });
+       }
+       return [];
+};
+
+/**
+ * Gets video media items from selected container.
+ *
+ * @method getVideoFromSelectedContainer
+ */
+RemoteContent.prototype.getVideoFromSelectedContainer = function() {
+       "use strict";
+       var self = this;
+       if (!!self.mediaContainerItemsComputed() && self.mediaContainerItemsComputed().length) {
+               return ko.utils.arrayFilter(self.mediaContainerItemsComputed(), function(mediaItem) {
+                       return mediaItem.type === "VIDEO";
+               });
+       }
+       return [];
+};
\ No newline at end of file
diff --git a/js/utils.js b/js/utils.js
new file mode 100644 (file)
index 0000000..33d0500
--- /dev/null
@@ -0,0 +1,118 @@
+/**
+ * @module MultimediaPlayerApplication
+ */
+
+/**
+ * Utility class with helper methods for Multimedia player.
+ *
+ * @class Utils
+ */
+var Utils = {
+       /**
+        * Provides default thumbnails for given media types.
+        *
+        * @method getDefaultThumbnailByType
+        * @param type {String} media type
+        */
+       getDefaultThumbnailByType : function(type) {
+               "use strict";
+               var thumbnail = "";
+               switch (type) {
+               case "AUDIO":
+                       thumbnail = "/images/audio-placeholder.jpg";
+                       break;
+               case "VIDEO":
+                       thumbnail = "/images/video-placeholder.jpg";
+                       break;
+               case "CONTAINER":
+                       thumbnail = "/images/container-placeholder.jpg";
+                       break;
+               default:
+                       thumbnail = "/images/default-placeholder.jpg";
+                       break;
+               }
+               return thumbnail;
+       },
+       /**
+        * Gets thumbnail path out of media item object if exists otherwise gets default placeholder by type.
+        *
+        * @method getThumbnailPath
+        * @param mediaItem {Object}  media item object
+        * @param type {String} media type
+        * @return {String} thumbnail path
+        */
+       getThumbnailPath : function(mediaItem, type) {
+               "use strict";
+               if (!!mediaItem) {
+                       if (!!mediaItem.thumbnailURIs && mediaItem.thumbnailURIs.length) {
+                               return mediaItem.thumbnailURIs[0];
+                       }
+                       if (!!mediaItem.type) {
+                               return this.getDefaultThumbnailByType(mediaItem.type);
+                       }
+               }
+               return this.getDefaultThumbnailByType(type || "");
+       },
+       /**
+        * Gets artist's name out of media item object.
+        *
+        * @method getArtistName
+        * @param mediaItem {Object} media item
+        * @return {String} artist name
+        */
+       getArtistName : function(mediaItem) {
+               "use strict";
+               if (!!mediaItem && !!mediaItem.artists && mediaItem.artists.length) {
+                       return mediaItem.artists.join(", ");
+               }
+               return "Unknown";
+       },
+       /**
+        * Gets album name out of media item object.
+        *
+        * @method getAlbumName
+        * @param mediaItem {Object} media item
+        * @return {String} album name
+        */
+       getAlbumName : function(mediaItem) {
+               "use strict";
+               if (!!mediaItem && !!mediaItem.album && mediaItem.album !== "") {
+                       return mediaItem.album;
+               }
+               return "Unknown";
+       },
+       /**
+        * Gets media item title out of media item object.
+        *
+        * @method getMediaItemTitle
+        * @param mediaItem {Object} media item
+        * @return {String} media title
+        */
+       getMediaItemTitle : function(mediaItem) {
+               "use strict";
+               if (!!mediaItem && !!mediaItem.title && mediaItem.title !== "") {
+                       return mediaItem.title;
+               }
+               if (!!mediaItem && !!mediaItem.name && mediaItem.name !== "") {
+                       return mediaItem.name;
+               }
+               return "Unknown";
+       },
+       /**
+        * Calls fullscreen request for given html element.
+        *
+        * @method launchFullScreen
+        * @param element {Object}
+        */
+       launchFullScreen : function(element) {
+               "use strict";
+               console.log("Launching full screen");
+               if (element.requestFullScreen) {
+                       element.requestFullScreen();
+               } else if (element.mozRequestFullScreen) {
+                       element.mozRequestFullScreen();
+               } else if (element.webkitRequestFullScreen) {
+                       element.webkitRequestFullScreen();
+               }
+       }
+};
\ No newline at end of file
diff --git a/packaging/html5-ui-multimediaplayer.changes b/packaging/html5-ui-multimediaplayer.changes
new file mode 100644 (file)
index 0000000..7b4dd89
--- /dev/null
@@ -0,0 +1,4 @@
+* Thu Mar 06 2014 brianjjones <brian.j.jones@intel.com> b3d35c5
+- Initial commit of the Multimediaplayer app
+
+
diff --git a/packaging/html5-ui-multimediaplayer.spec b/packaging/html5-ui-multimediaplayer.spec
new file mode 100644 (file)
index 0000000..dd7ef99
--- /dev/null
@@ -0,0 +1,36 @@
+Name:       html5_UI_Multimediaplayer
+Summary:    A proof of concept pure html5 UI
+Version:    0.0.1
+Release:    1
+Group:      Applications/System
+License:    Apache 2.0
+URL:        http://www.tizen.org
+Source0:    %{name}-%{version}.tar.bz2
+BuildRequires:  zip
+BuildRequires:  html5_UI_Common
+Requires:   wrt-installer
+Requires:   wrt-plugins-ivi
+
+%description
+A proof of concept pure html5 UI
+
+%prep
+%setup -q -n %{name}-%{version}
+
+%build
+
+make wgtPkg
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+%post
+    wrt-installer -i /opt/usr/apps/.preinstallWidgets/html5UIMultimediaplayer.wgt;
+
+%postun
+    wrt-installer -un html5POC07.Multimediaplayer
+
+%files
+%defattr(-,root,root,-)
+/opt/usr/apps/.preinstallWidgets/html5UIMultimediaplayer.wgt