1
0
Fork 0

The player is playing!

This commit is contained in:
Gregory Eremin 2012-09-02 02:03:19 +04:00
parent 43422624f1
commit 9f770892f8
29 changed files with 422 additions and 57 deletions

25
Gemfile
View File

@ -2,30 +2,31 @@ source :rubygems
gem "rails", "3.2.8"
gem "pg"
gem "thin"
group :assets do
gem "sass-rails", "~> 3.2.3"
gem "coffee-rails", "~> 3.2.1"
gem "therubyracer", platforms: :ruby
gem "hogan_assets"
gem "uglifier", ">= 1.0.3"
gem "jquery-rails"
gem "rails-backbone"
gem "hogan_assets"
gem "bourbon"
gem "bootstrap-sass", "~> 2.0.4.0"
gem "soundmanager-rails"
end
gem "jquery-rails"
group :development do
gem "awesome_print", require: "ap"
# Deploy with Capistrano
gem "capistrano"
end
# To use Jbuilder templates for JSON
gem "jbuilder"
gem "thin"
# Deploy with Capistrano
gem "capistrano"
gem "robbie", path: "../robbie"
gem "beatparser", path: "../beatparser"
gem "rails-backbone"
gem "eco"
gem 'bootstrap-sass', '~> 2.0.4.0'

View File

@ -44,6 +44,7 @@ GEM
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
awesome_print (1.0.2)
blankslate (2.1.2.4)
bootstrap-sass (2.0.4.0)
bourbon (2.1.1)
@ -63,11 +64,6 @@ GEM
execjs
coffee-script-source (1.3.3)
daemons (1.1.9)
eco (1.0.0)
coffee-script
eco-source
execjs
eco-source (1.1.0.rc.1)
ejs (1.0.0)
erubis (2.7.0)
eventmachine (0.12.10)
@ -148,6 +144,7 @@ GEM
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
soundmanager-rails (0.1.1)
sprockets (2.1.3)
hike (~> 1.2)
rack (~> 1.0)
@ -172,12 +169,12 @@ PLATFORMS
ruby
DEPENDENCIES
awesome_print
beatparser!
bootstrap-sass (~> 2.0.4.0)
bourbon
capistrano
coffee-rails (~> 3.2.1)
eco
hogan_assets
jbuilder
jquery-rails
@ -186,6 +183,7 @@ DEPENDENCIES
rails-backbone
robbie!
sass-rails (~> 3.2.3)
soundmanager-rails
therubyracer
thin
uglifier (>= 1.0.3)

View File

@ -1,18 +1,8 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require jquery
//= require jquery_ujs
//= require jquery.autocomplete
//= require soundmanager
//= require vk_music
//= require mustache
//= require hogan
//= require underscore
@ -20,5 +10,6 @@
//= require backbone_rails_sync
//= require backbone_datalink
//= require backbone/beat_haven
//= require bootstrap-dropdown
//= require_tree .

View File

@ -1,10 +1,10 @@
$ ->
$(".navbar-search input").focus ->
$(this).animate(width: 249)
$(".player").animate(width: 408)
$(".player").animate(width: 368)
$(".navbar-search input").blur ->
$(this).animate(width: 99)
$(".player").animate(width: 558)
$(".player").animate(width: 518)
window.desired = $(".navbar-search input").autocomplete
serviceUrl: "/api/search/complete"

View File

@ -9,24 +9,32 @@ window.BeatHaven =
Collections: {}
Routers: {}
Views: {}
player: null
Player: null
User: null
init: ->
new BeatHaven.Routers.Artist()
new BeatHaven.Routers.Album()
new BeatHaven.Routers.Search()
@player = new BeatHaven.Models.Player()
@Player = new BeatHaven.Models.Player()
@User = new BeatHaven.Models.User()
@VK = new BeatHaven.Models.VK()
@VK.init()
Backbone.history.start(pushState: true);
$("a").live "click", (e) ->
if $(this).attr("href").substr(0, 1) == "/"
e.preventDefault()
Backbone.history.navigate($(this).attr("href"), true)
return false
else
alert "Window close attempt!"
return false
true
false
log: (data) ->
console.log data
$ ->
BeatHaven.init()
# Setup shortcut
window.BH = window.BeatHaven

View File

@ -2,10 +2,63 @@ class BeatHaven.Models.Player extends Backbone.Model
playlist_on: false
playlist: null
tracks: null
current_track: null
initialize: ->
@playlist = new BeatHaven.Collections.Tracklist()
@tracks = new BeatHaven.Collections.Tracklist()
play: (track) ->
unless track?
if @current_track?
@current_track.get("sm_obj").resume()
else
this.play_something()
else
if @current_track?
@current_track.get("sm_obj").stop()
$(".player .progress-bar .bar").css(width: 0)
@current_track = track
@current_track.get("sm_obj").play()
$(".player .controls .play").css(display: "none")
$(".player .controls .pause").css(display: "inline-block")
pause: ->
return false unless @current_track?
@current_track.get("sm_obj").pause()
$(".player .controls .play").css(display: "inline-block")
$(".player .controls .pause").css(display: "none")
next: ->
return false unless @current_track?
if @playlist_on
# not implemented
else
nodes = @current_track.node().next()
return false unless nodes.length == 1
@tracks.get(parseInt($(nodes[0]).data("id"), 10)).play()
prev: ->
return false unless @current_track?
if @playlist_on
# not implemented
else
nodes = @current_track.node().prev()
return false unless nodes.length == 1
@tracks.get(parseInt($(nodes[0]).data("id"), 10)).play()
play_something: ->
nodes = $(".artist-page .tracks li[data-id]")
return false unless nodes.length > 0
@tracks.get(parseInt($(nodes[0]).data("id"), 10)).play()
update_title: (params) ->
$(".player .progress-bar .title").html("#{params.artists.join(', ')} — #{params.track}")
update_buffer_bar: (event) ->
false
update_progress_bar: (obj) ->
percent = obj.position / obj.duration * 100
$(".player .progress-bar .bar").css(width: "#{percent}%")

View File

@ -1,7 +1,43 @@
class BeatHaven.Models.Track extends Backbone.Model
play: ->
BeatHaven.player.update_title(
if @.get("sm_obj")?
this.start()
else
this.find_and_start()
start: ->
BH.Player.update_title(
artists: @.get("artists")
track: @.get("title")
)
unless @.get("sm_obj")?
this.add_to_library(autoload: true, autoplay: false)
BH.Player.play(this)
$(".artist-page .tracks li[data-id]").removeClass("now-playing")
this.node().addClass("now-playing")
find_and_start: ->
self = this
BH.VK.Music.search @.get("artists")[0], @.get("title"), @.get("length"), (url) ->
self.set(url: url)
self.start()
add_to_library: (params) ->
obj = soundManager.createSound
id: @.get("id")
url: @.get("url")
autoLoad: params.autoload
autoPlay: params.autoplay
whileloading: ->
BH.Player.update_buffer_bar(this)
whileplaying: ->
BH.Player.update_progress_bar(this)
onfinish: ->
BH.Player.next()
ondataerror: ->
BH.Player.next()
@.set("sm_obj", obj)
node: ->
$(".artist-page .tracks li[data-id='#{@.get("id")}']")

View File

@ -0,0 +1,27 @@
class BeatHaven.Models.User extends Backbone.Model
auth: ->
BH.log "Authenticating user ..."
this.query "/api/session/auth", {}, (response) ->
if response.error?
# report error
else
BH.User.set(JSON.parse(response.user))
if response.is_newbie
BH.log "Requesting user tracks from Vkontakte ..."
# BH.VK.set_favorites()
query: (path, params, callback) ->
query_params = $.extend {}, @.get("vk_session"), params
query_params.authenticity_token = $('meta[name="csrf-token"]').attr("content")
$.post path, query_params, callback
false
set_favorites: (tracks) ->
BH.log tracks
BH.log "Sending your Vkontakte media collection to BeatHaven ..."
this.query "/user/set_first_favorites", tracks: tracks, (response) ->
if response.error?
BH.log "Got error: #{response.error}"
else
BH.log "We believe your favorite artists are #{response.join(', ')}"

View File

@ -0,0 +1,43 @@
class BeatHaven.Models.VK extends Backbone.Model
session_length: 3600 # seconds
Music: null
popup: ->
alert(1)
init: ->
@Music = new VkMusic()
BH.log "Initializing Vkontakte API ..."
window.VK.init(apiId: VK_APP_ID)
BH.VK.auth()
# VK.Widgets.Like("vk-like", {type: "mini", height: 20, pageUrl: "http://beathaven.org/", text: "Like"})
auth: ->
BH.log "Requesting new Vkontakte session ..."
window.VK.Auth.getLoginStatus (response) ->
BH.VK.auth_info(response)
false
, 8
false
auth_info: (response) ->
if typeof response isnt "undefined" and response.session?
BH.User.set(vk_session: response.session)
BH.User.auth()
if response.session.expire?
expire_in = response.session.expire * 1000 - new Date().getTime()
# time is an illusion...
expire_in = @session_length * 1000
BH.log "Session will expire in #{Math.round(expire_in / 1000)} seconds"
setTimeout ->
BH.log "Session expired"
BH.VK.auth()
, expire_in + 1000
else
BH.log "Not authorized"
set_favorites: ->
window.VK.Api.call "audio.get", uid: BH.User.vk_id(), (response) ->
BH.User.set_favorites(response.response)

View File

@ -10,6 +10,7 @@
{{#album_tracks}}
<li data-id="{{track_id}}">
<a href="" class="btn btn-round track-play"><i class="icon-play"></i></a>
<a href="" class="btn btn-round track-pause"><i class="icon-pause"></i></a>
<div class="title">
<div class="inner">
<a href="{{track_url}}" class="track-link">{{track_title}}</a>

View File

@ -23,6 +23,7 @@
{{#album_tracks}}
<li data-id="{{track_id}}">
<a href="" class="btn btn-round track-play"><i class="icon-play"></i></a>
<a href="" class="btn btn-round track-pause"><i class="icon-pause"></i></a>
<div class="title">
<div class="inner">
<a href="{{track_url}}" class="track-link">{{track_title}}</a>

View File

@ -8,6 +8,6 @@ class BeatHaven.Views.AlbumShow extends Backbone.View
if typeof @model.get("album_tracks") != "undefined"
for track_info in @model.get("album_tracks")
track = new BeatHaven.Models.Track(track_info.meta)
BeatHaven.player.tracks.push(track)
BeatHaven.Player.tracks.push(track)
$(@el).html(@template.render(@model.toJSON()))
this

View File

@ -9,6 +9,6 @@ class BeatHaven.Views.ArtistShow extends Backbone.View
for album_info in @model.get("artist_albums")
for track_info in album_info.album_tracks
track = new BeatHaven.Models.Track(track_info.meta)
BeatHaven.player.tracks.push(track)
BeatHaven.Player.tracks.push(track)
$(@el).html(@template.render(@model.toJSON()))
this

View File

@ -0,0 +1,7 @@
$ ->
$(".auth a").bind "mouseup", (e) ->
e.preventDefault()
if BeatHaven.authenticated
alert("auth ok!!!!!")
else
BeatHaven.VK.popup()

View File

@ -0,0 +1,9 @@
$ ->
$(".player .controls .prev").live "click", (e) ->
BH.Player.prev()
$(".player .controls .play").live "click", (e) ->
BH.Player.play()
$(".player .controls .pause").live "click", (e) ->
BH.Player.pause()
$(".player .controls .next").live "click", (e) ->
BH.Player.next()

View File

@ -0,0 +1,5 @@
$ ->
$(".track-play").live "click", (e) ->
e.preventDefault()
id = parseInt($(this).parent().data("id"), 10)
BH.Player.tracks.get(id).play()

View File

@ -24,6 +24,23 @@
display: none;
}
&.now-playing {
a {
color: #126be8 !important;
text-shadow: 0 0 1px rgba(255, 255, 255, .2);
}
.track-play {
display: none;
}
.track-pause {
display: block;
i {
opacity: 1;
}
}
}
.btn-round {
width: 10px;
height: 22px;
@ -34,7 +51,7 @@
}
}
.track-play {
.track-play, .track-pause {
position: absolute;
top: 3px;
left: 5px;
@ -43,6 +60,9 @@
opacity: .3;
}
}
.track-pause {
display: none;
}
// Track title
.title {
@ -112,7 +132,7 @@
}
background-color: rgba(200, 200, 200, .3);
.track-play i {
.track-play i, .track-pause i {
opacity: 1;
}
.track-add {

View File

@ -6,6 +6,7 @@
@import "artist";
@import "album-track";
@import "player";
@import "auth";
@import url(http://fonts.googleapis.com/css?family=Lobster+Two:400,400italic,700);
@import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro);
@ -25,6 +26,10 @@ body {
.navbar-inner {
background: none;
@include background-image(linear-gradient(rgba(0, 0, 0, .05) 0, rgba(0, 0, 0, .4) 100%));
.container {
overflow: hidden;
}
}
a.brand {
margin-top: 1px;

View File

@ -0,0 +1,3 @@
.navbar .auth {
margin: 14px 10px 0 15px;
}

View File

@ -1,5 +1,5 @@
.player {
width: 558px;
width: 518px;
.controls {
float: left;
@ -8,6 +8,10 @@
a {
margin: 5px 6px 0 -9px;
}
.pause {
display: none;
}
}
.progress-bar {
position: relative;
@ -25,6 +29,7 @@
.bar {
position: absolute;
width: 0%;
height: 26px;
background-color: rgba(255, 255, 255, .1);
@include box-shadow(inset 0 0 2px rgba(255, 255, 255, .2));

View File

@ -0,0 +1,36 @@
module Api
class SessionController < ApplicationController
def auth
render json: { error: "Signature verification failed!" } unless request_valid?
user_name = "#{params[:user][:first_name]} #{params[:user][:last_name]}"
user = User.find_by_vk_id(params[:mid].to_i)
is_newbie = false
if user.nil?
user = User.create(name: user_name, vk_id: params[:mid].to_i)
is_newbie = true
elsif user.name != user_name
user.update_attributes(name: user_name)
end
render json: { user: user.dump_json, is_newbie: is_newbie }
end
private
def request_valid?
%w[ expire mid secret sid sig ].map(&:to_sym).each do |key|
raise "Parameter not set: #{key}" if params[key].nil?
end
validation_string = %w[ expire mid secret sid ].map{ |key|
"#{key}=#{params[key.to_sym]}"
}.join() << BeatHaven::Application.config.api_accounts["vk"]["api_secret"]
params[:sig] == Digest::MD5.hexdigest(validation_string)
end
end
end

12
app/models/user.rb Normal file
View File

@ -0,0 +1,12 @@
class User < ActiveRecord::Base
attr_accessible :lang, :name, :vk_id
def dump_json
Jbuilder.encode do |j|
j.id id
j.name name
j.lang lang
j.vk_id vk_id
end
end
end

View File

@ -0,0 +1,13 @@
<div class="player pull-right">
<div class="controls">
<a href="" class="btn btn-bh-dark prev"><i class="icon-backward icon-white"></i></a>
<a href="" class="btn btn-bh-dark play"><i class="icon-play icon-white"></i></a>
<a href="" class="btn btn-bh-dark pause"><i class="icon-pause icon-white"></i></a>
<a href="" class="btn btn-bh-dark next"><i class="icon-forward icon-white"></i></a>
</div>
<div class="progress-bar">
<div class="bar"></div>
<div class="title">Waiting...</div>
<div class="move-it"></div>
</div>
</div>

View File

@ -4,7 +4,11 @@
<title>BeatHaven</title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= javascript_include_tag "http://vkontakte.ru/js/api/openapi.js" %>
<%= csrf_meta_tags %>
<script type="text/javascript" charset="utf-8">
window.VK_APP_ID = 2335068;
</script>
</head>
<body>
@ -15,18 +19,10 @@
<form class="navbar-search pull-left">
<input type="search" placeholder="Search" class="search-query">
</form>
<div class="player pull-right">
<div class="controls">
<a href="" class="btn btn-bh-dark prev"><i class="icon-backward icon-white"></i></a>
<a href="" class="btn btn-bh-dark pause"><i class="icon-pause icon-white"></i></a>
<a href="" class="btn btn-bh-dark next"><i class="icon-forward icon-white"></i></a>
</div>
<div class="progress-bar">
<div class="bar" style="width: 42%"></div>
<div class="title">Foo Fighters &mdash; Alone + Easy Target</div>
<div class="move-it"></div>
</div>
<div class="auth pull-right">
<a href=""><i class="icon-user icon-white"></i></a>
</div>
<%= render partial: "player" %>
</div>
</div>
</div>

View File

@ -1,5 +1,6 @@
File.open("#{Rails.root}/config/api_keys.yml") do |file|
config = YAML.load(file.read)
BeatHaven::Application.config.api_accounts = config
LastFM.api_key = config["lastfm"]["api_key"]
LastFM.secret = config["lastfm"]["api_secret"]

View File

@ -7,6 +7,9 @@ BeatHaven::Application.routes.draw do
resources :search, only: [] do
collection { get :complete; get :wtfis }
end
resources :session, only: [] do
collection { post :auth }
end
end
match "/:path" => "application#main", constraints: { path: /.*/ }

View File

@ -0,0 +1,11 @@
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.integer :vk_id
t.string :lang, default: "ru"
t.timestamps
end
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120901111329) do
ActiveRecord::Schema.define(:version => 20120901191655) do
create_table "albums", :force => true do |t|
t.integer "artist_id"
@ -66,4 +66,12 @@ ActiveRecord::Schema.define(:version => 20120901111329) do
t.datetime "updated_at", :null => false
end
create_table "users", :force => true do |t|
t.string "name"
t.integer "vk_id"
t.string "lang", :default => "ru"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end

View File

@ -0,0 +1,72 @@
###
* Vkontakte (VK.com) API music search tool
* https://github.com/magnolia-fan/vkontakte_music_search
*
* Copyright 2011, Gregory Eremin
* Licensed under the MIT license.
* https://raw.github.com/magnolia-fan/vkontakte_music_search/master/LICENSE
###
class window.VkMusic
query_results: {}
search: (artist, track, duration, callback, return_all = false) ->
query = this.prepareQuery artist, track
if @query_results[query]? and not return_all
callback @query_results[query]
that = this
VK.Api.call 'audio.search', q: query, (r) ->
results = that.range r.response, artist, track, duration
top_result = null
if results.length > 0
top_result = results[0].url
that.query_results[query] = results
callback if return_all then results else top_result
range: (data, artist, track, duration) ->
if typeof duration is 'string'
duration = duration.split ':'
duration = parseInt(duration[0], 10) * 60 + parseInt(duration[1], 10)
for item, i in data
if typeof item isnt 'object'
continue
item.score = 0
item.artist = this.trim(item.artist)
item.title = this.trim(item.title)
score = 0
if item.artist.length > 0
if item.artist == artist
score += 10
else if item.artist.split(artist).length is 2
score += 5
else if item.title.split(artist).length is 2
score += 4
if item.artist.length > 0
if item.title == track
score += 10
else if item.title.split(track).length is 2
score += 5
if duration != 0 and parseInt(item.duration, 10) == duration
score += 15
else
delta = Math.abs parseInt(item.duration, 10) - duration
score += (10 - delta) if delta < 10
data[i].score = score
if data.length > 0
if typeof data[0] isnt 'object'
data.splice(0, 1)
data.sort (a, b) ->
b.score - a.score
data
prepareQuery: (artist, track) ->
artist+" "+track.replace(/\(.*\)/i, '').split('/')[0]
trim: (str) ->
while str.indexOf(' ') isnt -1
str = str.replace(' ', ' ')
if str.charAt(0) is ' '
str = str.substring(1)
if str.charAt(str.length - 1) is ' '
str = str.substring(0, str.length - 1)
str