├── .gitignore ├── images ├── controls.png ├── classic-logo.gif └── classic-controls.gif ├── tcmi.css ├── LICENSE ├── tcmi-classic.css ├── tcmi.min.js ├── README.md └── tcmi.js /.gitignore: -------------------------------------------------------------------------------- 1 | tracks 2 | -------------------------------------------------------------------------------- /images/controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiac/tcmi/master/images/controls.png -------------------------------------------------------------------------------- /images/classic-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiac/tcmi/master/images/classic-logo.gif -------------------------------------------------------------------------------- /images/classic-controls.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radiac/tcmi/master/images/classic-controls.gif -------------------------------------------------------------------------------- /tcmi.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 50px; 3 | } 4 | 5 | #tcmi { 6 | position: fixed; 7 | bottom: 0; 8 | width: 100%; 9 | background: #fff; 10 | border-top: 1px solid #eee; 11 | z-index: 100; 12 | height: 2em; 13 | line-height: 2em; 14 | } 15 | 16 | #tcmi > * { 17 | display: inline-block; 18 | vertical-align: middle; 19 | } 20 | 21 | .tcmi_logo, 22 | #tcmi.playing .tcmi_logo:hover { 23 | float: right; 24 | color: #004000; 25 | margin-right: 8px; 26 | } 27 | .tcmi_logo:hover, 28 | #tcmi.playing .tcmi_logo { 29 | color: #c00000; 30 | } 31 | 32 | 33 | .tcmi_select { 34 | color: #c00000; 35 | background: #fff; 36 | border: 1px solid #ccc; 37 | } 38 | .tcmi_select option { 39 | color: #004000; 40 | } 41 | 42 | .tcmi_play, .tcmi_pause { 43 | cursor: pointer; 44 | cursor: hand; 45 | margin: 0 8px; 46 | width: 12px; 47 | height: 12px; 48 | border: 0; 49 | overflow: hidden; 50 | text-indent: -999em; 51 | background: transparent url('/static/assets/tcmi/images/controls.png') no-repeat 50% 0; 52 | } 53 | .tcmi_pause { 54 | background-position: 50% -12px; 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TCMI is licensed under the MIT license 2 | ====================================== 3 | 4 | Copyright (c) 2013 Richard Terry, http://radiac.net/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | 13 | Licenses for included third-party components 14 | -------------------------------------------- 15 | 16 | The icons based on Entypo are licensed under CC BY 3.0: 17 | 18 | http://creativecommons.org/licenses/by-sa/3.0/ 19 | -------------------------------------------------------------------------------- /tcmi-classic.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 50px; 3 | } 4 | 5 | #tcmi { 6 | position: fixed; 7 | bottom: 0; 8 | width: 100%; 9 | height: 28px; 10 | line-height: 28px; 11 | background: #004200; 12 | border-top: 1px solid #fff; 13 | padding: 4px 0; 14 | z-index: 100; 15 | text-align: center; 16 | } 17 | 18 | #tcmi > * { 19 | display: inline-block; 20 | vertical-align: middle; 21 | } 22 | 23 | .tcmi_logo { 24 | position: absolute; 25 | left: 8px; 26 | width: 134px; 27 | height: 28px; 28 | overflow: hidden; 29 | text-indent: -999em; 30 | background: transparent url('/static/assets/tcmi/images/classic-logo.gif') no-repeat 0 0; 31 | } 32 | 33 | #tcmi.playing .tcmi_logo { 34 | background-position: 0 -28px; 35 | } 36 | 37 | .tcmi_select { 38 | background: #004200; 39 | color: #fff; 40 | border: 1px solid #fff; 41 | } 42 | 43 | .tcmi_play, .tcmi_pause { 44 | position: absolute; 45 | cursor: pointer; 46 | cursor: hand; 47 | right: 8px; 48 | width: 86px; 49 | height: 22px; 50 | top: 50%; 51 | margin-top: -11px; 52 | border: 0; 53 | overflow: hidden; 54 | text-indent: -999em; 55 | background: transparent url('/static/assets/tcmi/images/classic-controls.gif') no-repeat 0 0; 56 | } 57 | .tcmi_pause { 58 | background-position: 0 -22px; 59 | } 60 | -------------------------------------------------------------------------------- /tcmi.min.js: -------------------------------------------------------------------------------- 1 | !function(){function t(t,i){var n=new Date;n.setDate(n.getDate()+3650),document.cookie=[encodeURIComponent(t),"=",i,"; expires="+n.toUTCString(),"; path=/","https:"==window.location.protocol?"; secure":""].join("")}function i(t,i){for(var n,o=document.cookie.split("; "),e=0;n=o[e]&&o[e].split("=");e++)if(decodeURIComponent(n[0])===t)return n[1];return i}if(!window.TCMI&&window.Audio&&window.jQuery){var n=window.jQuery,o={path:"/tcmi/tracks/",tracks:[],firstTrack:0,singlePage:!0,singleFilter:new RegExp("(/|(^|/)[^\\.]+|\\.html?|\\.php)($|\\?)"),singleExclude:null,autoPlay:!0,onBuild:null,onPage:null,onError:function(t){alert(t)}},e=function(){var t=window.TCMI_conf||{},e=this;for(var a in o)this[a]=void 0!==t[a]?t[a]:o[a];if(0===this.tracks.length&&this.onError("No TCMI tracks defined"),this.firstTrack=Math.min(this.firstTrack,this.tracks.length-1),this.build(),this.render(),this.audio=new Audio,n(this.audio).on("play",function(t){e._playing()}).on("ended",function(t){e.next()}).on("error",function(t){var i,n=t.target.error;i=n.code===n.MEDIA_ERR_NETWORK||n.code===n.MEDIA_ERR_SRC_NOT_SUPPORTED?"file could not be found":n.code===n.MEDIA_ERR_DECODE?"your browser cannot play it":"unknown error "+n.code,e.onError("Error playing music: "+i)}),this.filetype=this.audio.canPlayType&&""!==this.audio.canPlayType('audio/ogg; codecs="vorbis"')?".ogg":".mp3",this.autoPlay&&"true"===i("tcmip",!0).toString()){var s=this.autoPlay;"function"==typeof s&&(s=s()),s&&this.load()}this.singlePage&&n(window).on("popstate",function(t){e.openPage(t.originalEvent.state.href,!0)})};e.prototype=n.extend(e.prototype,{$con:null,track:null,playing:!1,build:function(){var t,i,o=this.$con=n('
'),e=(this.$logo=n('TCMI').appendTo(o),this.$play=n('').text("Play").appendTo(o)),a=this.$pause=n('').text("Pause").hide().appendTo(o),s=this.$sel=n('').appendTo(o),r=this.tracks.length;for(t=0;r>t;t++)i=n("").attr("value",t).text(this.tracks[t][1]).appendTo(s);s.val(this.firstTrack),this.onBuild&&this.onBuild();var l=this;s.change(function(t){l.track=s.val(),l.load()}),e.click(function(t){l.play()}),a.click(function(t){l.pause()})},render:function(){if(this.$con.appendTo("body"),this.singlePage){var t=this,i=window.location.protocol+"//"+window.location.host+"/";n("a").click(function(n){if(t.playing&&this.href&&-1!==this.href.indexOf(i)){if("function"==typeof t.singleFilter){if(!t.singleFilter(this.href))return}else if(null!==t.singleFilter&&!this.href.match(t.singleFilter))return;if("function"==typeof t.singleExclude){if(t.singleExclude(this.href))return}else if(null!==t.singleExclude&&this.href.match(t.singleExclude))return;n.preventDefault(),t.openPage(this.href)}})}},openPage:function(t,i){var o=this;this.onPage&&this.onPage(t),n.get(t,function(e,a,s){return"error"==a?void o.onError("Could not load page: "+s.status+" "+s.statusText):(o.$con.detach(),n("body").html(e),n("head title").text(n("body title").text()),i||window.history.pushState({href:t},"",t),void o.render())})},_load:function(){null===this.track&&(this.track=this.firstTrack),this.audio.setAttribute("src",this.path+this.tracks[this.track][0]+this.filetype),this.audio.load()},load:function(){this._load(),this.play()},next:function(){this.track++,this.track>=this.tracks.length&&(this.track=0),this.$sel.val(this.track),this.load()},play:function(){null===this.track&&this._load(),this.audio.play()},_playing:function(){this.$con.addClass("playing"),this.$play.hide(),this.$pause.show(),this.playing=!0,t("tcmip",!0)},pause:function(){this.audio.pause(),this.$con.removeClass("playing"),this.$play.show(),this.$pause.hide(),this.playing=!1,t("tcmip",!1)}}),n(function(){window.TCMI||(window.TCMI=new e)})}}(); 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TCMI - Tacky Christmas Music Interface 2 | ====================================== 3 | 4 | The TCMI is a tacky christmas music interface for your website. 5 | 6 | Whether it's the music or the interface which is tacky is a decision for the 7 | reader. 8 | 9 | 10 | Overview 11 | -------- 12 | 13 | Christmas is a time of joy, and what better way to spread joy than to force 14 | your website visitors to listen to Christmas music? 15 | 16 | TCMI adds a simple interface to your page, plays MP3 or OGG files at your 17 | visitors, and fills them with Christmas spirit. 18 | 19 | 20 | 21 | Adding TCMI to your site 22 | ------------------------ 23 | 24 | 1. Upload the files in this repository to somewhere on your website. These 25 | examples assume you'll put it at `/tcmi` (but it can go anywhere) 26 | 27 | 2. Upload your collection of christmas music - each file must be there twice, 28 | in `.mp3` and `.ogg` format. 29 | * TCMI assume you'll put it at `/tcmi/tracks`, but this can be changed by 30 | setting `path` in `TCMI_conf` (see [Configuring TCMI](#configuring-tcmi) 31 | below). 32 | * Bear in mind you're responsible for any licensing issues 33 | * An excellent collection of tacky christmas music can be downloaded from 34 | http://radiac.net/projects/tcmi/ 35 | * Top tip: to convert MP3 files to OGG, try: 36 | ``ffmpeg -i "mytrack.mp3" -acodec libvorbis "mytrack.ogg"`` 37 | 38 | 3. Now just load the code and CSS in the `head` of each page, and configure it; 39 | see [Configuring TCMI](#configuring-tcmi) below. 40 | 41 | Example: 42 | 43 | 44 | ... 45 | 46 | ... 47 | 48 | 49 | 54 | ... 55 | 56 | 57 | 58 | * TCMI requires jQuery, so if your site doesn't already use that you'll need 59 | to get it from http://jquery.com/download/ 60 | * If you want the classic TCMI 1.0 look, use `tcmi-classic.css` instead of 61 | `tcmi.css`. If you want to run it in a frame, create a page with just the 62 | TCMI and set `singlePage` to `false`. 63 | * If you want to interact with TCMI from your own code, you can access it on 64 | `window.TCMI`. See [Internals](#internals) below to see what you 65 | can do. 66 | 67 | 68 | Configuring TCMI 69 | ---------------- 70 | 71 | You configure TCMI by setting the global variable `TCMI_conf`. In the example 72 | above, this was set directly in the head; it can of course go anywhere in your 73 | site's JavaScript, as long as it is set before the DOM ready event fires. 74 | 75 | Options: 76 | 77 | `path` 78 | 79 | * Path to directory containing tracks 80 | * Must finish in a `/` 81 | * Default: `/tcmi/tracks/` 82 | 83 | 84 | `tracks` 85 | 86 | * Array of track tuples, [filename, title] 87 | * This must be set otherwise TCMI will raise an error 88 | * Default: `[]` 89 | 90 | 91 | `firstTrack` 92 | 93 | * Index of first track to play, starting at `0` 94 | * Allows you to specify a play order in `tracks` 95 | * Default: `0` 96 | 97 | 98 | `singlePage` 99 | 100 | * Boolean. If `true`, keep the site on a single page 101 | * This will hijack clicks on `` tags, to load same-domain pages without 102 | changing the page. This lets your music play continuously while your visitor 103 | is on your site. 104 | * This will have no effect on form submissions - they will make your music 105 | restart. 106 | * Set this to `false` if you don't want TCMI to interfere with links, or want 107 | to run it on its own page (ie in a frame). 108 | * Default: `true` 109 | 110 | 111 | `singleFilter` 112 | 113 | * A string, RegExp or function to determine if a link should be hijacked when 114 | `singlePage === true` 115 | * If it is a string or RegExp, a match will mean the link can be hijacked; if 116 | it does not match, the link will load as normal 117 | * If it is a function, it will be passed the `href`, and must return a boolean; 118 | if it returns true the link can be hijacked, if false the link will load as 119 | normal 120 | * This is after links to other domains are already filtered out, but before 121 | `singleExclude` is checked 122 | * Set to null to allow all 123 | * Default: `new RegExp('(/|(^|/)[^\\.]+|\\.html?|\\.php)($|\\?)')` 124 | * That is anything ending in a `/`, any path that ends without a file 125 | extension (ie `/path/to/page`), or `.html`, `.htm` and `.php` 126 | 127 | 128 | `singleExclude` 129 | 130 | * Like `singleFilter`, only in reverse - a match means that the file will not 131 | be hijacked. 132 | * Set to null to allow all 133 | * Default: `null` 134 | 135 | 136 | `autoPlay` 137 | 138 | * A boolean or function to determine if the music will start playing as soon as 139 | the page loads. 140 | * If `true`, start playing immediately. 141 | * If it is a function, it should return a truthy value to start playing 142 | immediately. 143 | * It is highly recommended that you help your visitors get into the Christmas 144 | spirit by setting this to ``true``. 145 | * Default: `true` 146 | 147 | 148 | `onBuild` 149 | 150 | * Callback if you want to customise the elements in the TCMI interface. 151 | * Passed no arguments. See the [Internals section](#internals) below to see 152 | what you can do. 153 | * Default: `null` 154 | 155 | 156 | `onPage` 157 | 158 | * Callback at the start of a page request when `singlePage === true`. 159 | * Passed a single argument, the `href` of the page being requested. 160 | * You could set this if you want to replace page content with a loading spinner 161 | - just make sure to hide it and re-display the old content in `onError`. 162 | * Default: `null` 163 | 164 | 165 | `onError` 166 | 167 | * Callback to display errors 168 | * Passed a single argument, the error message as a string. 169 | * Default: `function (msg) { alert(msg); }` 170 | 171 | 172 | Example configuration: 173 | 174 | window.TCMI_conf = { 175 | path: '/static/tcmi/tracks/', 176 | tracks: [ 177 | ["deckhall", "Deck The Halls"], 178 | ["jinglebells", "Jingle Bells"] 179 | ], 180 | firstTrack: 1, // jinglebells 181 | singlePage: false, 182 | onPage: function (href) { 183 | $('main') 184 | .hide() 185 | .after('