├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── build ├── index.html ├── javascripts │ ├── example.js │ └── karaoke.js └── stylesheets │ └── karaoke.css ├── config.rb ├── license.txt └── source ├── index.html.slim ├── javascripts ├── example.js.coffee └── karaoke.js.coffee ├── layouts └── layout.html.slim └── stylesheets └── karaoke.css.sass /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | 10 | # Ignore cache 11 | /.sass-cache 12 | /.cache 13 | 14 | # Ignore .DS_store file 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # If you do not have OpenSSL installed, update 2 | # the following line to use "http://" instead 3 | source 'https://rubygems.org' 4 | 5 | gem "middleman", "~>3.3.7" 6 | 7 | # Live-reloading plugin 8 | gem "middleman-livereload", "~> 3.1.0" 9 | 10 | # For faster file watcher updates on Windows: 11 | gem "wdm", "~> 0.1.0", :platforms => [:mswin, :mingw] 12 | 13 | # Windows does not come with time zone data 14 | gem "tzinfo-data", platforms: [:mswin, :mingw] 15 | 16 | gem 'middleman-slim' 17 | gem 'coffee' 18 | gem 'sass' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.1.9) 5 | i18n (~> 0.6, >= 0.6.9) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.1) 9 | tzinfo (~> 1.1) 10 | celluloid (0.16.0) 11 | timers (~> 4.0.0) 12 | chunky_png (1.3.3) 13 | coffee (0.0.5) 14 | httparty 15 | json_pure 16 | thor 17 | coffee-script (2.3.0) 18 | coffee-script-source 19 | execjs 20 | coffee-script-source (1.9.0) 21 | compass (1.0.3) 22 | chunky_png (~> 1.2) 23 | compass-core (~> 1.0.2) 24 | compass-import-once (~> 1.0.5) 25 | rb-fsevent (>= 0.9.3) 26 | rb-inotify (>= 0.9) 27 | sass (>= 3.3.13, < 3.5) 28 | compass-core (1.0.3) 29 | multi_json (~> 1.0) 30 | sass (>= 3.3.0, < 3.5) 31 | compass-import-once (1.0.5) 32 | sass (>= 3.2, < 3.5) 33 | em-websocket (0.5.1) 34 | eventmachine (>= 0.12.9) 35 | http_parser.rb (~> 0.6.0) 36 | erubis (2.7.0) 37 | eventmachine (1.0.7) 38 | execjs (2.3.0) 39 | ffi (1.9.6) 40 | haml (4.0.6) 41 | tilt 42 | hike (1.2.3) 43 | hitimes (1.2.2) 44 | hooks (0.4.0) 45 | uber (~> 0.0.4) 46 | http_parser.rb (0.6.0) 47 | httparty (0.13.3) 48 | json (~> 1.8) 49 | multi_xml (>= 0.5.2) 50 | i18n (0.6.11) 51 | json (1.8.2) 52 | json_pure (1.8.2) 53 | kramdown (1.5.0) 54 | listen (2.8.5) 55 | celluloid (>= 0.15.2) 56 | rb-fsevent (>= 0.9.3) 57 | rb-inotify (>= 0.9) 58 | middleman (3.3.7) 59 | coffee-script (~> 2.2) 60 | compass (>= 1.0.0, < 2.0.0) 61 | compass-import-once (= 1.0.5) 62 | execjs (~> 2.0) 63 | haml (>= 4.0.5) 64 | kramdown (~> 1.2) 65 | middleman-core (= 3.3.7) 66 | middleman-sprockets (>= 3.1.2) 67 | sass (>= 3.4.0, < 4.0) 68 | uglifier (~> 2.5) 69 | middleman-core (3.3.7) 70 | activesupport (~> 4.1.0) 71 | bundler (~> 1.1) 72 | erubis 73 | hooks (~> 0.3) 74 | i18n (~> 0.6.9) 75 | listen (>= 2.7.9, < 3.0) 76 | padrino-helpers (~> 0.12.3) 77 | rack (>= 1.4.5, < 2.0) 78 | rack-test (~> 0.6.2) 79 | thor (>= 0.15.2, < 2.0) 80 | tilt (~> 1.4.1, < 2.0) 81 | middleman-livereload (3.1.1) 82 | em-websocket (>= 0.2.0) 83 | middleman-core (>= 3.0.2) 84 | multi_json (~> 1.0) 85 | rack-livereload 86 | middleman-slim (0.2.1) 87 | middleman (>= 3.2) 88 | slim (>= 2.0) 89 | middleman-sprockets (3.4.1) 90 | middleman-core (>= 3.3) 91 | sprockets (~> 2.12.1) 92 | sprockets-helpers (~> 1.1.0) 93 | sprockets-sass (~> 1.3.0) 94 | minitest (5.5.1) 95 | multi_json (1.10.1) 96 | multi_xml (0.5.5) 97 | padrino-helpers (0.12.4) 98 | i18n (~> 0.6, >= 0.6.7) 99 | padrino-support (= 0.12.4) 100 | tilt (~> 1.4.1) 101 | padrino-support (0.12.4) 102 | activesupport (>= 3.1) 103 | rack (1.6.0) 104 | rack-livereload (0.3.15) 105 | rack 106 | rack-test (0.6.3) 107 | rack (>= 1.0) 108 | rb-fsevent (0.9.4) 109 | rb-inotify (0.9.5) 110 | ffi (>= 0.5.0) 111 | sass (3.4.11) 112 | slim (3.0.2) 113 | temple (~> 0.7.3) 114 | tilt (>= 1.3.3, < 2.1) 115 | sprockets (2.12.3) 116 | hike (~> 1.2) 117 | multi_json (~> 1.0) 118 | rack (~> 1.0) 119 | tilt (~> 1.1, != 1.3.0) 120 | sprockets-helpers (1.1.0) 121 | sprockets (~> 2.0) 122 | sprockets-sass (1.3.1) 123 | sprockets (~> 2.0) 124 | tilt (~> 1.1) 125 | temple (0.7.5) 126 | thor (0.19.1) 127 | thread_safe (0.3.4) 128 | tilt (1.4.1) 129 | timers (4.0.1) 130 | hitimes 131 | tzinfo (1.2.2) 132 | thread_safe (~> 0.1) 133 | uber (0.0.13) 134 | uglifier (2.7.0) 135 | execjs (>= 0.3.0) 136 | json (>= 1.8.0) 137 | 138 | PLATFORMS 139 | ruby 140 | 141 | DEPENDENCIES 142 | coffee 143 | middleman (~> 3.3.7) 144 | middleman-livereload (~> 3.1.0) 145 | middleman-slim 146 | sass 147 | tzinfo-data 148 | wdm (~> 0.1.0) 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Karaoke JS 2 | A simple JavaScript-powered karaoke script. The script takes in a multi-dimensional array of lyrics 3 | with specific timings for each word to be highlighted binded to a designated audio player element. 4 | 5 | # Dependencies 6 | The script uses the Middleman framework. 7 | The app uses the following: 8 | 14 | 15 | Although the script uses jQuery, the word-highlight animations are carried out with CSS3. 16 | 17 | # Setup 18 | To build off the app using the current framework, one would need to know how to use Middleman to do so. 19 | It should be as easy as installing the framework using gem install middleman onto one's system and running middleman server or 20 | middleman build. More information can be found on the framework's website. 21 |
22 |
23 | Otherwise, it would be perfectly fine to just edit the files in the /build directory in this repository to do simple tasks 24 | such as changing the audio player element to point to a specific track and adding the lyrics to the JavaScript. The only downside is that comments for the JavaScript are written in the CoffeeScript files. It might be helpful when reviewing example.js in the /build to check the example.js.coffee file in the /source folder for additional information. 25 |
26 |
27 | To get karaoke demo going, one must set up the lyrics variable, set the settings variable (if desired), and then instantiate the Karaoke class. If no settings are supplied the script uses defaults. 28 |
29 |
30 | The highlight styling can be changed in the karaoke.css file. 31 | 32 | # Credits 33 | This application was built by Jake Larson to be implemented in the The 34 | Changing Story (to be released) eBook written by Linda Buturian, Susan Andre, and Thomas Nechodomu in the 35 | College of Education and Human Development at the 36 | University of Minnesota — Twin Cities. 37 | 38 | # License 39 | Karaoke JS is released under the MIT License. 40 | -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | Karaoke

Karaoke Demo

-------------------------------------------------------------------------------- /build/javascripts/example.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | $(function() { 3 | var lyrics, settings; 4 | lyrics = [[[1.0, "Here"], [1.3, "is"], [1.45, "the"], [1.55, "first"], [1.7, "line"]], [[1.92, "And"], [2.08, "this"], [2.25, "is"], [2.4, "the"], [2.7, "second!"]], [[3.1, "This"], [3.47, "song"], [3.8, "is"], [3.95, "not"], [4.15, "real"]], [[4.4, "At"], [4.5, "I"], [4.6, "hope"], [4.7, "it's"], [4.9, "not!"]]]; 5 | settings = { 6 | 'last-word-highlight-time': 5.5, 7 | 'scroll-animation-time': .1, 8 | 'karaoke-player-elem': $('audio#karaoke-player'), 9 | 'karaoke-lyrics-elem': $('#karaoke-lyrics') 10 | }; 11 | return window.karaoke = new window.Karaoke(lyrics, settings); 12 | }); 13 | 14 | }).call(this); 15 | -------------------------------------------------------------------------------- /build/javascripts/karaoke.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 3 | 4 | window.Karaoke = (function() { 5 | function Karaoke(lyrics, settings) { 6 | var default_settings; 7 | if (settings == null) { 8 | settings = void 0; 9 | } 10 | this.moveToLine = __bind(this.moveToLine, this); 11 | this.playWord = __bind(this.playWord, this); 12 | this.cancelWordTimers = __bind(this.cancelWordTimers, this); 13 | this.initWordTimers = __bind(this.initWordTimers, this); 14 | this.initLyrics = __bind(this.initLyrics, this); 15 | this.initPlayerBinds = __bind(this.initPlayerBinds, this); 16 | this.initVars = __bind(this.initVars, this); 17 | this.settings = settings; 18 | default_settings = { 19 | 'last-word-highlight-time': 1.0, 20 | 'scroll-animation-time': .1, 21 | 'karaoke-player-elem': $('audio#karaoke-player'), 22 | 'karaoke-lyrics-elem': $('#karaoke-lyrics') 23 | }; 24 | if (typeof settings !== 'undefined') { 25 | $.each(default_settings, (function(_this) { 26 | return function(setting, value) { 27 | return _this.settings[setting] = typeof _this.settings[setting] !== 'undefined' ? _this.settings[setting] : default_settings[setting]; 28 | }; 29 | })(this)); 30 | } else { 31 | this.settings = default_settings; 32 | } 33 | this.lyrics = lyrics; 34 | this.initVars(); 35 | this.initPlayerBinds(); 36 | this.initLyrics(); 37 | } 38 | 39 | Karaoke.prototype.initVars = function() { 40 | this.player_elem = this.settings['karaoke-player-elem'][0]; 41 | this.lyrics_elem = this.settings['karaoke-lyrics-elem']; 42 | return this.word_timers = []; 43 | }; 44 | 45 | Karaoke.prototype.initPlayerBinds = function() { 46 | this.player_elem.addEventListener('play', (function(_this) { 47 | return function() { 48 | if (_this.word_timers.length > 0) { 49 | _this.cancelWordTimers(); 50 | } 51 | return _this.initWordTimers(); 52 | }; 53 | })(this)); 54 | this.player_elem.addEventListener('pause', (function(_this) { 55 | return function() { 56 | return _this.cancelWordTimers(); 57 | }; 58 | })(this)); 59 | return this.player_elem.addEventListener('seeked', (function(_this) { 60 | return function() { 61 | _this.cancelWordTimers(); 62 | return _this.initWordTimers(); 63 | }; 64 | })(this)); 65 | }; 66 | 67 | Karaoke.prototype.initLyrics = function() { 68 | $.each(this.lyrics, (function(_this) { 69 | return function(line_index, line) { 70 | var build_line; 71 | build_line = "

"; 72 | $.each(line, function(word_index, word) { 73 | if (word_index > 0) { 74 | build_line += " "; 75 | } 76 | return build_line += "" + word[1] + ""; 77 | }); 78 | build_line += "

"; 79 | return _this.lyrics_elem.append(build_line); 80 | }; 81 | })(this)); 82 | return this.moveToLine(0); 83 | }; 84 | 85 | Karaoke.prototype.initWordTimers = function() { 86 | var current_time, moved_to_line; 87 | current_time = this.player_elem.currentTime; 88 | moved_to_line = false; 89 | return $.each(this.lyrics, (function(_this) { 90 | return function(line_index, line) { 91 | return $.each(line, function(word_index, word_piece) { 92 | var get_next_time, time_offset, time_until_next, timing, word; 93 | timing = word_piece[0]; 94 | word = word_piece[1]; 95 | if (timing > _this.player_elem.currentTime) { 96 | if (!moved_to_line) { 97 | _this.moveToLine(line_index); 98 | moved_to_line = true; 99 | } 100 | time_offset = (timing - _this.player_elem.currentTime) * 1000; 101 | if (_this.lyrics[line_index].length > word_index + 1) { 102 | get_next_time = _this.lyrics[line_index][word_index + 1][0]; 103 | } else if (_this.lyrics.length > line_index + 1) { 104 | get_next_time = _this.lyrics[line_index + 1][0][0]; 105 | } else { 106 | get_next_time = timing + _this.settings['last-word-highlight-time']; 107 | } 108 | time_until_next = get_next_time - timing; 109 | return _this.word_timers.push(setTimeout(function() { 110 | return _this.playWord(line_index, word_index, time_until_next); 111 | }, time_offset)); 112 | } 113 | }); 114 | }; 115 | })(this)); 116 | }; 117 | 118 | Karaoke.prototype.cancelWordTimers = function() { 119 | $.each(this.word_timers, (function(_this) { 120 | return function(timer) { 121 | return clearTimeout(_this.word_timers[timer]); 122 | }; 123 | })(this)); 124 | return this.word_timers = []; 125 | }; 126 | 127 | Karaoke.prototype.playWord = function(line_index, word_index, time_offset) { 128 | var line_elem, word_elem; 129 | line_elem = this.lyrics_elem.find('.line')[line_index]; 130 | word_elem = $(line_elem).find('.word')[word_index]; 131 | $(word_elem).addClass('active'); 132 | setTimeout(function() { 133 | return $(word_elem).removeClass('active'); 134 | }, time_offset * 1000); 135 | return this.moveToLine(line_index); 136 | }; 137 | 138 | Karaoke.prototype.moveToLine = function(line_index) { 139 | var height; 140 | height = $(this.lyrics_elem.find('.line')[line_index]).outerHeight(); 141 | return this.lyrics_elem.animate({ 142 | scrollTop: height * line_index 143 | }, this.settings['scroll-animation-time'] * 1000); 144 | }; 145 | 146 | return Karaoke; 147 | 148 | })(); 149 | 150 | }).call(this); 151 | -------------------------------------------------------------------------------- /build/stylesheets/karaoke.css: -------------------------------------------------------------------------------- 1 | /* line 1, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 2 | body { 3 | background-color: #ecf0f1; 4 | font-family: "Open Sans"; 5 | font-size: 16px; } 6 | 7 | /* line 6, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 8 | h1 { 9 | color: #8e44ad; 10 | font-family: "Ubuntu"; } 11 | 12 | /* line 10, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 13 | #karaoke-panel { 14 | width: 80%; 15 | margin: 0 auto; 16 | text-align: center; } 17 | /* line 15, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 18 | #karaoke-panel #karaoke-lyrics { 19 | margin: 50px auto; 20 | height: 168px; 21 | overflow: hidden; } 22 | /* line 20, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 23 | #karaoke-panel #karaoke-lyrics p.line { 24 | line-height: 16px; 25 | padding-top: 20px; 26 | padding-bottom: 20px; 27 | margin: 0; 28 | color: #95a5a6; } 29 | /* line 27, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 30 | #karaoke-panel #karaoke-lyrics p.line:first-child { 31 | margin-top: 56px; } 32 | /* line 30, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 33 | #karaoke-panel #karaoke-lyrics p.line span.word { 34 | transition: all linear 0.5s; } 35 | /* line 33, /Users/Jake/dev/karaoke-js/source/stylesheets/karaoke.css.sass */ 36 | #karaoke-panel #karaoke-lyrics p.line span.word.active { 37 | color: #8e44ad; 38 | text-decoration: underline; } 39 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # Compass 3 | ### 4 | 5 | # Change Compass configuration 6 | # compass_config do |config| 7 | # config.output_style = :compact 8 | # end 9 | 10 | ### 11 | # Page options, layouts, aliases and proxies 12 | ### 13 | 14 | # Per-page layout changes: 15 | # 16 | # With no layout 17 | # page "/path/to/file.html", :layout => false 18 | # 19 | # With alternative layout 20 | # page "/path/to/file.html", :layout => :otherlayout 21 | # 22 | # A path which all have the same layout 23 | # with_layout :admin do 24 | # page "/admin/*" 25 | # end 26 | 27 | # Proxy pages (http://middlemanapp.com/basics/dynamic-pages/) 28 | # proxy "/this-page-has-no-template.html", "/template-file.html", :locals => { 29 | # :which_fake_page => "Rendering a fake page with a local variable" } 30 | 31 | ### 32 | # Helpers 33 | ### 34 | 35 | # Automatic image dimensions on image_tag helper 36 | # activate :automatic_image_sizes 37 | 38 | # Reload the browser automatically whenever files change 39 | # configure :development do 40 | # activate :livereload 41 | # end 42 | 43 | # Methods defined in the helpers block are available in templates 44 | # helpers do 45 | # def some_helper 46 | # "Helping" 47 | # end 48 | # end 49 | 50 | set :css_dir, 'stylesheets' 51 | 52 | set :js_dir, 'javascripts' 53 | 54 | set :images_dir, 'images' 55 | 56 | # Build-specific configuration 57 | configure :build do 58 | # For example, change the Compass output style for deployment 59 | # activate :minify_css 60 | 61 | # Minify Javascript on build 62 | # activate :minify_javascript 63 | 64 | # Enable cache buster 65 | # activate :asset_hash 66 | 67 | # Use relative URLs 68 | # activate :relative_assets 69 | 70 | # Or use a different image path 71 | # set :http_prefix, "/Content/images/" 72 | end 73 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jake Larson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /source/index.html.slim: -------------------------------------------------------------------------------- 1 | h1 Karaoke Demo 2 | 3 | #karaoke-panel 4 | 5 | #karaoke-lyrics 6 | 7 | audio#karaoke-player controls=true 8 | source src='/audio/example.mp3' type='audio/mp3' -------------------------------------------------------------------------------- /source/javascripts/example.js.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | # The lyrics list must be organized like so: 3 | # lyrics = [ 4 | # [ # first line 5 | # [2.5, "first",], #time, first word 6 | # [2.8, "second"] #time, second word 7 | # ] 8 | # ] 9 | # 10 | 11 | lyrics = [ 12 | [ 13 | [1.0, "Here"] 14 | [1.3, "is"] 15 | [1.45, "the"] 16 | [1.55, "first"] 17 | [1.7, "line"] 18 | ] 19 | [ 20 | [1.92, "And"] 21 | [2.08, "this"] 22 | [2.25, "is"] 23 | [2.4, "the"] 24 | [2.7, "second!"] 25 | ] 26 | [ 27 | [3.1, "This"] 28 | [3.47, "song"] 29 | [3.8, "is"] 30 | [3.95, "not"] 31 | [4.15, "real"] 32 | ] 33 | [ 34 | [4.4, "At"] 35 | [4.5, "I"] 36 | [4.6, "hope"] 37 | [4.7, "it's"] 38 | [4.9, "not!"] 39 | ] 40 | ] 41 | 42 | settings = { 43 | 'last-word-highlight-time': 5.5 # how long the last word will be higlighted (seconds) 44 | 'scroll-animation-time': .1 # speed of the scrolling (seconds) 45 | 'karaoke-player-elem': $('audio#karaoke-player') 46 | 'karaoke-lyrics-elem': $('#karaoke-lyrics') 47 | } 48 | 49 | window.karaoke = new window.Karaoke(lyrics, settings) -------------------------------------------------------------------------------- /source/javascripts/karaoke.js.coffee: -------------------------------------------------------------------------------- 1 | class window.Karaoke 2 | constructor: (lyrics, settings = undefined) -> 3 | @settings = settings 4 | default_settings = { 5 | 'last-word-highlight-time': 1.0 6 | 'scroll-animation-time': .1 7 | 'karaoke-player-elem': $('audio#karaoke-player') 8 | 'karaoke-lyrics-elem': $('#karaoke-lyrics') 9 | } 10 | 11 | unless typeof settings == 'undefined' 12 | $.each default_settings, (setting, value) => 13 | @settings[setting] = if (typeof @settings[setting] != 'undefined') then @settings[setting] else default_settings[setting] 14 | else 15 | @settings = default_settings 16 | 17 | @lyrics = lyrics 18 | @initVars() 19 | @initPlayerBinds() 20 | @initLyrics() 21 | 22 | initVars: => 23 | @player_elem = @settings['karaoke-player-elem'][0] 24 | @lyrics_elem = @settings['karaoke-lyrics-elem'] 25 | @word_timers = [] 26 | 27 | initPlayerBinds: => 28 | @player_elem.addEventListener 'play', () => 29 | if @word_timers.length > 0 30 | @cancelWordTimers() 31 | 32 | @initWordTimers() 33 | 34 | @player_elem.addEventListener 'pause', () => 35 | @cancelWordTimers() 36 | 37 | @player_elem.addEventListener 'seeked', () => 38 | @cancelWordTimers() 39 | @initWordTimers() 40 | 41 | initLyrics: => 42 | $.each @lyrics, (line_index, line) => 43 | build_line = "

" 44 | 45 | $.each line, (word_index, word) => 46 | if word_index > 0 47 | build_line += " " 48 | build_line += "#{word[1]}" 49 | 50 | build_line += "

" 51 | @lyrics_elem.append build_line 52 | 53 | @moveToLine(0) 54 | 55 | initWordTimers: => 56 | current_time = @player_elem.currentTime 57 | moved_to_line = false 58 | 59 | $.each @lyrics, (line_index, line) => 60 | $.each line, (word_index, word_piece) => 61 | timing = word_piece[0] 62 | word = word_piece[1] 63 | 64 | if timing > @player_elem.currentTime 65 | if !moved_to_line 66 | @moveToLine(line_index) 67 | moved_to_line = true 68 | 69 | time_offset = (timing - @player_elem.currentTime) * 1000 70 | 71 | if @lyrics[line_index].length > word_index + 1 # next word in current line 72 | get_next_time = @lyrics[line_index][word_index+1][0] 73 | 74 | else if @lyrics.length > line_index + 1 # next word in new line 75 | get_next_time = @lyrics[line_index+1][0][0] 76 | 77 | else # last word in lyrics 78 | get_next_time = timing + @settings['last-word-highlight-time'] 79 | 80 | time_until_next = get_next_time - timing 81 | 82 | @word_timers.push(setTimeout(() => 83 | @playWord line_index, word_index, time_until_next 84 | , time_offset)) 85 | 86 | cancelWordTimers: => 87 | $.each @word_timers, (timer) => 88 | clearTimeout(@word_timers[timer]) 89 | 90 | @word_timers = [] 91 | 92 | playWord: (line_index, word_index, time_offset) => 93 | line_elem = @lyrics_elem.find('.line')[line_index] 94 | word_elem = $(line_elem).find('.word')[word_index] 95 | $(word_elem).addClass('active') 96 | 97 | setTimeout () -> 98 | $(word_elem).removeClass('active') 99 | , time_offset*1000 100 | 101 | @moveToLine(line_index) 102 | 103 | moveToLine: (line_index) => 104 | height = $(@lyrics_elem.find('.line')[line_index]).outerHeight() # all lines should be equal height for now 105 | @lyrics_elem.animate({scrollTop: (height * (line_index))}, @settings['scroll-animation-time']*1000) -------------------------------------------------------------------------------- /source/layouts/layout.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta charset="utf-8" 5 | meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" 6 | title== current_page.data.title || "Karaoke" 7 | 8 | == stylesheet_link_tag "karaoke" 9 | == javascript_include_tag "https://code.jquery.com/jquery-2.1.3.min.js", "karaoke", "example" 10 | link href='http://fonts.googleapis.com/css?family=Open+Sans|Ubuntu' rel='stylesheet' type='text/css' 11 | 12 | body class="#{page_classes}" 13 | == yield 14 | -------------------------------------------------------------------------------- /source/stylesheets/karaoke.css.sass: -------------------------------------------------------------------------------- 1 | body 2 | background-color: #ecf0f1 3 | font-family: 'Open Sans' 4 | font-size: 16px 5 | 6 | h1 7 | color: #8e44ad 8 | font-family: 'Ubuntu' 9 | 10 | #karaoke-panel 11 | width: 80% 12 | margin: 0 auto 13 | text-align: center 14 | 15 | #karaoke-lyrics 16 | margin: 50px auto 17 | height: 168px 18 | overflow: hidden 19 | 20 | p.line 21 | line-height: 16px 22 | padding-top: 20px 23 | padding-bottom: 20px 24 | margin: 0 25 | color: #95a5a6 26 | 27 | &:first-child 28 | margin-top: 56px 29 | 30 | span.word 31 | transition: all linear .5s 32 | 33 | &.active 34 | color: #8e44ad 35 | text-decoration: underline 36 | --------------------------------------------------------------------------------