├── .ruby-version ├── Gemfile ├── test ├── lib │ ├── playa_test.rb │ └── playa │ │ ├── application_test.rb │ │ ├── views │ │ ├── help_view_test.rb │ │ ├── progress_view_test.rb │ │ ├── startup_view_test.rb │ │ ├── status_view_test.rb │ │ └── playlist_view_test.rb │ │ ├── controllers │ │ └── controller_test.rb │ │ ├── helpers │ │ └── helpers_test.rb │ │ └── models │ │ ├── player_test.rb │ │ ├── track_collection_test.rb │ │ └── track_test.rb ├── support │ └── parkwalk.mp3 └── test_helper.rb ├── screenshot.png ├── Rakefile ├── Guardfile ├── .gitignore ├── bin └── playa ├── lib ├── playa │ ├── helpers │ │ └── helpers.rb │ ├── models │ │ ├── track_collection.rb │ │ ├── track.rb │ │ └── player.rb │ ├── views │ │ ├── status_view.rb │ │ ├── startup_view.rb │ │ ├── help_view.rb │ │ ├── progress_view.rb │ │ └── playlist_view.rb │ ├── controllers │ │ └── controller.rb │ ├── application.rb │ └── log.rb └── playa.rb ├── LICENSE.txt ├── README.md └── playa.gemspec /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.2 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /test/lib/playa_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | end 5 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlaking/playa/HEAD/screenshot.png -------------------------------------------------------------------------------- /test/support/parkwalk.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlaking/playa/HEAD/test/support/parkwalk.mp3 -------------------------------------------------------------------------------- /test/lib/playa/application_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe Application do 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/lib/playa/views/help_view_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe HelpView do 5 | 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/lib/playa/controllers/controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe Controller do 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/lib/playa/views/progress_view_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe ProgressView do 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/lib/playa/views/startup_view_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe StartupView do 5 | 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/lib/playa/views/status_view_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe StatusView do 5 | 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | task default: :test 5 | 6 | Rake::TestTask.new do |t| 7 | t.libs.push 'lib' 8 | t.libs.push 'test' 9 | t.pattern = 'test/**/*_test.rb' 10 | end 11 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :minitest, all_after_pass: true, env: { 'no_simplecov' => true } do 2 | watch(%r{^test/(.*)_test\.rb}) 3 | watch(%r{^lib/(.+)\.rb}) do |m| 4 | "test/lib/#{m[1]}_test.rb" 5 | end 6 | watch(%r{^test/test_helper\.rb}) { 'test' } 7 | end 8 | -------------------------------------------------------------------------------- /test/lib/playa/views/playlist_view_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe PlaylistView do 5 | describe '#show' do 6 | it 'returns output suitable for Vedeu to parse' do 7 | skip 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | -------------------------------------------------------------------------------- /bin/playa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib') 3 | $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') 4 | end 5 | 6 | -> { its -> { a } } 7 | trap('INT') { exit! } 8 | 9 | require 'playa' 10 | 11 | args = ARGV.dup 12 | 13 | play_directory = args.empty? || args.nil? ? [Dir.pwd] : args 14 | 15 | Playa::Application.start(play_directory) 16 | -------------------------------------------------------------------------------- /lib/playa/helpers/helpers.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | module Helpers 3 | def duration(track) 4 | human(track.duration.ceil) 5 | end 6 | 7 | def remaining(track, player) 8 | human((track.duration - player.counter).floor) 9 | end 10 | 11 | private 12 | 13 | def human(seconds) 14 | mm, ss = seconds.divmod(60) 15 | hh, mm = mm.divmod(60) 16 | mins = mm.to_s.rjust(2, '0') 17 | secs = ss.to_s.rjust(2, '0') 18 | 19 | [mins, secs].join(":") 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/playa.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | end 3 | 4 | require 'bundler/setup' 5 | require 'audite' 6 | require 'mp3info' 7 | require 'vedeu' 8 | 9 | require 'playa/log' 10 | require 'playa/models/track' 11 | require 'playa/models/track_collection' 12 | require 'playa/models/player' 13 | require 'playa/helpers/helpers' 14 | require 'playa/views/help_view' 15 | require 'playa/views/playlist_view' 16 | require 'playa/views/progress_view' 17 | require 'playa/views/status_view' 18 | require 'playa/views/startup_view' 19 | require 'playa/controllers/controller' 20 | require 'playa/application' 21 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'pry' 3 | require 'minitest/autorun' 4 | require 'minitest/hell' 5 | require 'minitest/pride' 6 | 7 | SimpleCov.start do 8 | command_name 'MiniTest::Spec' 9 | add_filter '/test/' 10 | end unless ENV['no_simplecov'] 11 | 12 | require 'playa' 13 | 14 | require 'mocha/setup' 15 | 16 | GC.disable 17 | 18 | # commented out by default (makes tests slower) 19 | # require 'minitest/reporters' 20 | # Minitest::Reporters.use!( 21 | # Minitest::Reporters::DefaultReporter.new({ color: true, slow_count: 5 }), 22 | # Minitest::Reporters::SpecReporter.new 23 | # ) 24 | -------------------------------------------------------------------------------- /lib/playa/models/track_collection.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class TrackCollection 3 | def initialize(args = []) 4 | @args = args 5 | end 6 | 7 | def tracks 8 | @_tracks ||= files.reduce([]) do |acc, file| 9 | acc << Track.new(file) 10 | acc 11 | end 12 | end 13 | 14 | def files 15 | @_files ||= Dir.glob(recursive).select do |file| 16 | File.file?(file) && File.extname(file) == '.mp3' 17 | end 18 | end 19 | 20 | private 21 | 22 | attr_reader :args 23 | 24 | def recursive 25 | directory + '/**/*' 26 | end 27 | 28 | def directory 29 | args.first || '.' 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/playa/views/status_view.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class StatusView 3 | include Vedeu 4 | 5 | def show 6 | render do 7 | view 'status' do 8 | line do 9 | foreground('#ff0000') { text " \u{25B2}" } 10 | foreground('#ffffff') { text ' Prev ' } 11 | 12 | foreground('#ff0000') { text "\u{25BC}" } 13 | foreground('#ffffff') { text ' Next ' } 14 | 15 | foreground('#ff0000') { text "\u{21B2}" } 16 | foreground('#ffffff') { text ' Play ' } 17 | 18 | foreground('#ff0000') { text "\u{2395}" } 19 | foreground('#ffffff') { text ' Pause ' } 20 | 21 | foreground('#ff0000') { text 'Q' } 22 | foreground('#ffffff') { text ' Quit ' } 23 | end 24 | end 25 | end 26 | end 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/lib/playa/helpers/helpers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | class HelpersTest 5 | include Helpers 6 | end 7 | 8 | describe Helpers do 9 | let(:receiver) { HelpersTest.new } 10 | let(:track) { mock('Track') } 11 | 12 | before { track.stubs(:duration).returns(4321.6543) } 13 | 14 | describe '#duration' do 15 | it 'returns a human readable version of the track duration' do 16 | receiver.duration(track).must_equal('12:02') 17 | end 18 | end 19 | 20 | describe '#remaining' do 21 | let(:player) { mock('Player') } 22 | 23 | before { player.stubs(:counter).returns(42) } 24 | 25 | it 'returns a human readable version of the remaining time for a ' \ 26 | 'playing track' do 27 | receiver.remaining(track, player).must_equal('11:19') 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/playa/views/startup_view.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class StartupView 3 | include Vedeu 4 | 5 | def show 6 | trigger(:_clear_) 7 | 8 | render do 9 | view 'progress' do 10 | line do 11 | stream do 12 | text 'Welcome to Playa.' 13 | align centre 14 | width use('progress').width 15 | end 16 | end 17 | end 18 | 19 | view 'playlist' do 20 | line '' 21 | line '' 22 | line 'Playa is a simple, interactive mp3 player for your terminal.' 23 | end 24 | 25 | view 'status' do 26 | line do 27 | stream do 28 | text 'Press `s` to begin.' 29 | align centre 30 | width use('progress').width 31 | end 32 | end 33 | end 34 | end 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/playa/models/track.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class Track 3 | def initialize(file) 4 | @file = file 5 | end 6 | 7 | def attributes 8 | { 9 | filename: filename, 10 | title: title, 11 | artist: artist, 12 | album: album, 13 | track_number: track_number, 14 | duration: duration, 15 | bitrate: bitrate 16 | } 17 | end 18 | 19 | def filename 20 | id_tags.filename 21 | end 22 | 23 | def title 24 | id_tags.tag.title || filename # TODO: this will include the path 25 | end 26 | 27 | def artist 28 | id_tags.tag.artist || '' 29 | end 30 | 31 | def album 32 | id_tags.tag.album || '' 33 | end 34 | 35 | def track_number 36 | id_tags.tag.tracknum || 0 37 | end 38 | 39 | def duration 40 | id_tags.length || 0 41 | end 42 | 43 | def bitrate 44 | id_tags.bitrate || 0 45 | end 46 | 47 | private 48 | 49 | attr_reader :file 50 | 51 | def id_tags 52 | @_id_tags ||= Mp3Info.open(file) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Gavin Laking 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/playa/models/player.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class Player 3 | include Vedeu 4 | 5 | def initialize 6 | event(:forward) { forward if playing? } 7 | event(:rewind) { rewind if playing? } 8 | event(:toggle) { toggle } 9 | event(:play) do |track| 10 | stop if playing? 11 | 12 | open(track) 13 | 14 | play 15 | end 16 | end 17 | 18 | def play 19 | player.start_stream 20 | end 21 | 22 | def stop 23 | player.stop_stream 24 | end 25 | 26 | def rewind 27 | player.rewind(5) 28 | end 29 | 30 | def forward 31 | player.forward(5) 32 | end 33 | 34 | def toggle 35 | if playing? 36 | stop 37 | else 38 | play 39 | end 40 | end 41 | 42 | def playing? 43 | player.active || false 44 | end 45 | 46 | def counter 47 | player.position 48 | end 49 | 50 | def progress 51 | if playing? 52 | counter / @track.duration 53 | else 54 | 0 55 | end 56 | end 57 | 58 | def level 59 | player.level 60 | end 61 | 62 | def events 63 | player.events 64 | end 65 | 66 | def track 67 | @track 68 | end 69 | 70 | private 71 | 72 | def open(track) 73 | @track = track 74 | 75 | player.load(track.filename) 76 | end 77 | 78 | def player 79 | @_player ||= Audite.new 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Code Climate](https://codeclimate.com/github/gavinlaking/playa.png)](https://codeclimate.com/github/gavinlaking/playa) 2 | 3 | # Playa 4 | 5 | Plays mp3s from a directory using Ruby. An example app using Vedeu ( https://github.com/gavinlaking/vedeu ). 6 | 7 | ![Exciting Screenshot](https://raw.github.com/gavinlaking/playa/master/screenshot.png) 8 | 9 | 10 | ## Requirements 11 | 12 | - Portaudio >= 19 13 | - Mpg123 >= 1.14 14 | 15 | 16 | ### OSX Dependency Installation 17 | 18 | brew install portaudio 19 | brew install mpg123 20 | 21 | ### Debian / Ubuntu Dependency Installation 22 | 23 | sudo apt-get install libjack0 libjack-dev 24 | sudo apt-get install libportaudiocpp0 portaudio19-dev libmpg123-dev 25 | 26 | #### Gem Installation 27 | 28 | gem install playa 29 | 30 | or 31 | 32 | sudo gem install playa 33 | 34 | 35 | ## Usage 36 | 37 | Play all .mp3 files in the current working directory: 38 | 39 | playa 40 | 41 | or, specify a directory to play from: 42 | 43 | playa /path/to/mp3s 44 | 45 | 46 | ## Contributing 47 | 48 | 1. Fork it ( http://github.com/gavinlaking/playa/fork ) 49 | 2. Clone it 50 | 3. `bundle` 51 | 4. `rake` or `bundle exec guard` 52 | 5. Create your feature branch (`git checkout -b my-new-feature`) 53 | 6. Write some tests, write some code, have some fun 54 | 7. Commit your changes (`git commit -am 'Add some feature'`) 55 | 8. Push to the branch (`git push origin my-new-feature`) 56 | 9. Create a new pull request 57 | -------------------------------------------------------------------------------- /lib/playa/views/help_view.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class HelpView 3 | include Vedeu 4 | 5 | def show 6 | trigger(:_clear_) 7 | 8 | render do 9 | view 'help' do 10 | line do 11 | colour foreground: '#aadd00' 12 | stream do 13 | align centre 14 | width use('help').width 15 | text 'Wow, you found the really useful help page!' 16 | end 17 | end 18 | 19 | line '' 20 | 21 | line do 22 | colour foreground: '#ffffff' 23 | stream do 24 | align centre 25 | width use('help').width 26 | text 'Whilst a track is playing you can press:' 27 | end 28 | end 29 | 30 | line '' 31 | 32 | line do 33 | foreground('#ff0000') { text " \u{25C0}" } 34 | foreground('#ffffff') { text ' to rewind 5 seconds' } 35 | end 36 | 37 | line do 38 | foreground('#ff0000') { text " \u{25B6}" } 39 | foreground('#ffffff') { text ' to go forward 5 seconds' } 40 | end 41 | 42 | line '' 43 | line '' 44 | 45 | line do 46 | colour foreground: '#ffff00' 47 | stream do 48 | align centre 49 | width use('help').width 50 | text 'Press `p` to return to Playa.' 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/playa/controllers/controller.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class Controller 3 | include Vedeu 4 | 5 | event :complete do 6 | trigger(:_menu_next_, 'playlist') 7 | trigger(:_menu_select_, 'playlist') 8 | trigger(:select, trigger(:_menu_selected_, 'playlist')) 9 | trigger(:update) 10 | end 11 | 12 | event(:_initialize_) { trigger(:show_startup) } 13 | event(:select) { |track| trigger(:play, track) } 14 | event(:show_startup) { StartupView.new.show } 15 | event(:show_help) { HelpView.new.show } 16 | 17 | event :update do 18 | PlaylistView.new.show 19 | 20 | trigger(:_refresh_playlist_) 21 | end 22 | 23 | def initialize(args = []) 24 | @args = args 25 | @player = Player.new 26 | @player.events.on(:position_change) { trigger(:progress_update) } 27 | @player.events.on(:complete) { trigger(:complete) } 28 | 29 | event :show_player do 30 | trigger(:_clear_) 31 | 32 | PlaylistView.new.show 33 | StatusView.new.show 34 | ProgressView.new(@player).show 35 | 36 | trigger(:_refresh_group_player_) 37 | end 38 | 39 | event(:progress_update, { delay: 0.5 }) do 40 | ProgressView.new(@player).show 41 | 42 | trigger(:_refresh_progress_) 43 | end 44 | 45 | menu('playlist') { items(tracks) } 46 | end 47 | 48 | private 49 | 50 | attr_reader :args 51 | 52 | def tracks 53 | @tracks ||= TrackCollection.new(args).tracks 54 | end 55 | 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/playa/views/progress_view.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class ProgressView 3 | include Vedeu 4 | include Playa::Helpers 5 | 6 | def initialize(player) 7 | @player = player 8 | end 9 | 10 | def show 11 | if player.track 12 | track_loaded 13 | 14 | else 15 | no_track_loaded 16 | 17 | end 18 | end 19 | 20 | private 21 | 22 | attr_reader :player 23 | 24 | def track_loaded 25 | render do 26 | view 'progress' do 27 | line do 28 | stream do 29 | width progress_width 30 | text progress_bar 31 | end 32 | 33 | stream do 34 | width timer_width 35 | text timer 36 | align :right 37 | end 38 | end 39 | end 40 | end 41 | end 42 | 43 | def no_track_loaded 44 | render do 45 | view 'progress' do 46 | line do 47 | stream do 48 | width view_width 49 | text ' ' 50 | end 51 | end 52 | end 53 | end 54 | end 55 | 56 | def progress_width 57 | view_width - timer_width - 1 58 | end 59 | 60 | def progress_bar 61 | "\u{25FC}" * (player.progress * progress_width).ceil 62 | end 63 | 64 | def timer_width 65 | timer.size + 1 66 | end 67 | 68 | def timer 69 | remaining(player.track, player) 70 | end 71 | 72 | def view_width 73 | Vedeu.use('progress').width 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /playa.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'playa' 7 | spec.version = '0.1.0' 8 | spec.authors = ['Gavin Laking'] 9 | spec.email = ['gavinlaking@gmail.com'] 10 | spec.summary = 'Plays mp3s from a directory.' 11 | spec.description = 'An example app using Vedeu.' 12 | spec.homepage = 'https://github.com/gavinlaking/playa' 13 | spec.license = 'MIT' 14 | 15 | spec.files = `git ls-files -z`.split("\x0") 16 | spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(/^(test|spec|features)\//) 18 | spec.require_paths = ['lib'] 19 | 20 | spec.add_development_dependency 'bundler', '~> 1.6' 21 | spec.add_development_dependency 'guard', '2.6.1' 22 | spec.add_development_dependency 'guard-minitest', '2.3.2' 23 | spec.add_development_dependency 'minitest', '5.4.1' 24 | spec.add_development_dependency 'minitest-reporters', '1.0.5' 25 | spec.add_development_dependency 'mocha', '1.1.0' 26 | spec.add_development_dependency 'pry', '0.10.1' 27 | spec.add_development_dependency 'rake', '10.3.2' 28 | spec.add_development_dependency 'simplecov', '0.9.0' 29 | 30 | spec.add_dependency 'audite', '0.3.0' 31 | spec.add_dependency 'ruby-mp3info', '0.8.4' 32 | spec.add_dependency 'vedeu', '0.2.4' 33 | end 34 | -------------------------------------------------------------------------------- /test/lib/playa/models/player_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe Player do 5 | let(:player) { Player.new } 6 | let(:track) { stub(filename: 'test/support/parkwalk.mp3') } 7 | 8 | describe '#initialize' do 9 | it 'returns an instance of itself' do 10 | Player.new.must_be_instance_of(Player) 11 | end 12 | end 13 | 14 | describe '#play' do 15 | it 'returns an instance of Portaudio' do 16 | player.play.must_be_instance_of(Portaudio) 17 | end 18 | end 19 | 20 | describe '#stop' do 21 | it 'returns nil when nothing is playing' do 22 | player.stop.must_be_instance_of(NilClass) 23 | end 24 | end 25 | 26 | describe '#rewind' do 27 | it '' do 28 | skip # spf 29 | player.rewind.must_be_instance_of(NilClass) 30 | end 31 | end 32 | 33 | describe '#forward' do 34 | it '' do 35 | skip # spf 36 | player.forward.must_be_instance_of(NilClass) 37 | end 38 | end 39 | 40 | describe '#toggle' do 41 | it '' do 42 | player.toggle.must_be_instance_of(Portaudio) 43 | end 44 | end 45 | 46 | describe '#playing?' do 47 | it 'returns false when nothing is playing' do 48 | player.playing?.must_be_instance_of(FalseClass) 49 | end 50 | 51 | it 'returns true when a track is playing' do 52 | skip 53 | player.playing?.must_be_instance_of(TrueClass) 54 | end 55 | end 56 | 57 | describe '#counter' do 58 | it '' do 59 | skip 60 | player.counter.must_be_instance_of(NilClass) 61 | end 62 | end 63 | 64 | describe '#level' do 65 | it 'returns the volume level of the audio currently under ' \ 66 | 'the play head' do 67 | player.level.must_be_instance_of(Float) 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/lib/playa/models/track_collection_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe TrackCollection do 5 | describe '#initialize' do 6 | it 'returns an instance of itself' do 7 | TrackCollection.new.must_be_instance_of(TrackCollection) 8 | end 9 | end 10 | 11 | describe '#tracks' do 12 | it 'returns a collection of Track objects' do 13 | collection = TrackCollection.new 14 | collection.tracks.must_be_instance_of(Array) 15 | collection.tracks.first.must_be_instance_of(Track) 16 | end 17 | end 18 | 19 | describe '#files' do 20 | it 'returns an empty list of files when the directory ' \ 21 | 'contains no mp3s' do 22 | collection = TrackCollection.new(['/some/path']) 23 | Dir.stub(:glob, []) do 24 | collection.files.must_be_empty 25 | end 26 | end 27 | 28 | it 'returns only the mp3s when the directory contains ' \ 29 | 'multiple file types' do 30 | files = [ 31 | '/some/path/dance.mp3', 32 | '/some/path/README.txt', 33 | '/some/path/dubstep.mp3' 34 | ] 35 | collection = TrackCollection.new(['/some/path']) 36 | Dir.stub(:glob, files) do 37 | File.stub(:file?, true) do 38 | collection.files.must_equal([ 39 | '/some/path/dance.mp3', 40 | '/some/path/dubstep.mp3' 41 | ]) 42 | end 43 | end 44 | end 45 | 46 | it 'returns a list of files for the specified directory' do 47 | files = [ 48 | '/some/path/dance.mp3', 49 | '/some/path/electro.mp3', 50 | '/some/path/dubstep.mp3' 51 | ] 52 | collection = TrackCollection.new 53 | Dir.stub(:glob, files) do 54 | File.stub(:file?, true) do 55 | collection.files.must_equal(files) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/playa/application.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | def self.log(message) 3 | Playa::Log.logger.debug(message) 4 | end 5 | 6 | class Application 7 | include Vedeu 8 | 9 | interface 'help' do 10 | centred true 11 | colour foreground: '#ffffff', background: '#000000' 12 | group 'help' 13 | height 9 14 | width 60 15 | end 16 | 17 | interface 'playlist' do 18 | colour foreground: '#afd700', background: '#000000' 19 | width 60 20 | height 5 21 | centred true 22 | group 'player' 23 | end 24 | 25 | interface 'progress' do 26 | colour foreground: '#005aff', background: '#000000' 27 | width 60 28 | height 1 29 | y { use('playlist').north(2) } 30 | x { use('playlist').left } 31 | centred false 32 | delay 1.0 33 | group 'player' 34 | end 35 | 36 | interface 'status' do 37 | colour foreground: '#d70000', background: '#000000' 38 | width 60 39 | height 1 40 | y { use('playlist').south(1) } 41 | x { use('playlist').left } 42 | centred false 43 | group 'player' 44 | end 45 | 46 | keys do 47 | key('p', 's') { trigger(:show_player) } 48 | key('?') { trigger(:show_help) } 49 | key(' ') { trigger(:toggle) } # pause/unpause 50 | key('h', :left) { trigger(:rewind) } 51 | key('l', :right) { trigger(:forward) } 52 | 53 | key('k', :up) do 54 | trigger(:_menu_prev_, 'playlist') 55 | trigger(:update) 56 | end 57 | 58 | key('j', :down) do 59 | trigger(:_menu_next_, 'playlist') 60 | trigger(:update) 61 | end 62 | 63 | key(:enter) do 64 | trigger(:_menu_select_, 'playlist') 65 | trigger(:select, trigger(:_menu_selected_, 'playlist')) 66 | trigger(:update) 67 | end 68 | end 69 | 70 | configure do 71 | colour_mode 16777216 72 | interactive! 73 | raw! 74 | end 75 | 76 | def self.start(args = []) 77 | Controller.new(args) 78 | 79 | Vedeu::Launcher.new(args).execute! 80 | rescue Errno::EMFILE 81 | puts "Playa does not support this number of files." 82 | puts "Please see https://github.com/gavinlaking/playa/issues/11" 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/lib/playa/models/track_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Playa 4 | describe Track do 5 | let(:file) { '/some/path/electro.mp3' } 6 | let(:tag) do 7 | stub( 8 | title: 'eee-lectro', 9 | artist: 'Gavin Laking + Various', 10 | album: 'That night at Sankeys', 11 | tracknum: 3 12 | ) 13 | end 14 | let(:mp3info) do 15 | stub( 16 | filename: '/some/path/electro.mp3', 17 | tag: tag, 18 | length: 3623.0270625, 19 | bitrate: 320 20 | ) 21 | end 22 | let(:track) { Track.new(file) } 23 | 24 | before do 25 | Mp3Info.stubs(:open).returns(mp3info) 26 | end 27 | 28 | describe '#initialize' do 29 | it 'returns an instance of itself' do 30 | Track.new(file).must_be_instance_of(Track) 31 | end 32 | end 33 | 34 | describe '#attributes' do 35 | it 'returns a collection of attributes' do 36 | track.attributes.must_equal( 37 | filename: '/some/path/electro.mp3', 38 | title: 'eee-lectro', 39 | artist: 'Gavin Laking + Various', 40 | album: 'That night at Sankeys', 41 | track_number: 3, 42 | duration: 3623.0270625, 43 | bitrate: 320 44 | ) 45 | end 46 | end 47 | 48 | describe '#filename' do 49 | it 'returns the filename' do 50 | track.filename.must_equal('/some/path/electro.mp3') 51 | end 52 | end 53 | 54 | describe '#title' do 55 | it 'returns the title' do 56 | track.title.must_equal('eee-lectro') 57 | end 58 | end 59 | 60 | describe '#artist' do 61 | it 'returns the artist' do 62 | track.artist.must_equal('Gavin Laking + Various') 63 | end 64 | end 65 | 66 | describe '#album' do 67 | it 'returns the album' do 68 | track.album.must_equal('That night at Sankeys') 69 | end 70 | end 71 | 72 | describe '#track_number' do 73 | it 'returns the track_number' do 74 | track.track_number.must_equal(3) 75 | end 76 | end 77 | 78 | describe '#duration' do 79 | it 'returns the duration' do 80 | track.duration.must_equal(3623.0270625) 81 | end 82 | end 83 | 84 | describe '#bitrate' do 85 | it 'returns the bitrate' do 86 | track.bitrate.must_equal(320) 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/playa/views/playlist_view.rb: -------------------------------------------------------------------------------- 1 | module Playa 2 | class PlaylistView 3 | include Vedeu 4 | include Playa::Helpers 5 | 6 | def show 7 | render do 8 | view 'playlist' do 9 | playlist_menu.each do |sel, cur, item| 10 | if sel && cur 11 | line do 12 | stream do 13 | width title_width(item) 14 | text "\u{25B6}> #{item.title}" 15 | end 16 | 17 | stream do 18 | width timer_width(item) 19 | text "#{timer(item)}" 20 | align :right 21 | end 22 | end 23 | 24 | elsif cur 25 | line do 26 | stream do 27 | width title_width(item) 28 | text " > #{item.title}" 29 | end 30 | 31 | stream do 32 | width timer_width(item) 33 | text "#{timer(item)}" 34 | align :right 35 | end 36 | end 37 | 38 | elsif sel 39 | line do 40 | stream do 41 | width title_width(item) 42 | text "\u{25B6} #{item.title}" 43 | end 44 | 45 | stream do 46 | width timer_width(item) 47 | text "#{timer(item)}" 48 | align :right 49 | end 50 | end 51 | 52 | else 53 | line do 54 | stream do 55 | width title_width(item) 56 | text " #{item.title}" 57 | end 58 | 59 | stream do 60 | width timer_width(item) 61 | text "#{timer(item)}" 62 | align :right 63 | end 64 | end 65 | 66 | end 67 | end 68 | end 69 | end 70 | end 71 | 72 | private 73 | 74 | def title_width(item) 75 | view_width - timer_width(item) - 1 76 | end 77 | 78 | def timer_width(item) 79 | timer(item).size + 1 80 | end 81 | 82 | def timer(item) 83 | duration(item) 84 | end 85 | 86 | def view_width 87 | Vedeu.use('playlist').width 88 | end 89 | 90 | def playlist_menu 91 | @playlist_menu = Vedeu.trigger(:_menu_view_, 'playlist') 92 | end 93 | 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/playa/log.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'logger' 3 | require 'time' 4 | 5 | module Playa 6 | # :nocov: 7 | class MonoLogger < Logger 8 | # Create a trappable Logger instance. 9 | # 10 | # @param logdev [String|IO] The filename (String) or IO object (typically 11 | # STDOUT, STDERR or an open file). 12 | # @param shift_age [] Number of old log files to keep, or frequency of 13 | # rotation (daily, weekly, monthly). 14 | # @param shift_size [] Maximum log file size (only applies when shift_age 15 | # is a number). 16 | # 17 | # @example 18 | # Logger.new(name, shift_age = 7, shift_size = 1048576) 19 | # Logger.new(name, shift_age = 'weekly') 20 | # 21 | def initialize(logdev, shift_age=nil, shift_size=nil) 22 | @progname = nil 23 | @level = DEBUG 24 | @default_formatter = Formatter.new 25 | @formatter = nil 26 | @logdev = nil 27 | if logdev 28 | @logdev = LocklessLogDevice.new(logdev) 29 | end 30 | end 31 | 32 | class LocklessLogDevice < LogDevice 33 | def initialize(log = nil) 34 | @dev = @filename = @shift_age = @shift_size = nil 35 | if log.respond_to?(:write) and log.respond_to?(:close) 36 | @dev = log 37 | else 38 | @dev = open_logfile(log) 39 | @dev.sync = true 40 | @filename = log 41 | end 42 | end 43 | 44 | def write(message) 45 | @dev.write(message) 46 | rescue Exception => ignored 47 | warn("log writing failed. #{ignored}") 48 | end 49 | 50 | def close 51 | @dev.close rescue nil 52 | end 53 | 54 | private 55 | 56 | def open_logfile(filename) 57 | if (FileTest.exist?(filename)) 58 | open(filename, (File::WRONLY | File::APPEND)) 59 | else 60 | create_logfile(filename) 61 | end 62 | end 63 | 64 | def create_logfile(filename) 65 | logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT)) 66 | logdev.sync = true 67 | add_log_header(logdev) 68 | logdev 69 | end 70 | 71 | def add_log_header(file) 72 | file.write( 73 | "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] 74 | ) 75 | end 76 | end 77 | end 78 | 79 | class Log 80 | 81 | # @return [TrueClass] 82 | def self.logger 83 | @logger ||= MonoLogger.new(filename).tap do |log| 84 | log.formatter = proc do |_, time, _, message| 85 | time.utc.iso8601 + ": " + message + "\n" 86 | end 87 | end 88 | end 89 | 90 | private 91 | 92 | # @api private 93 | # @return [String] 94 | def self.filename 95 | @_filename ||= directory + '/playa.log' 96 | end 97 | 98 | # @api private 99 | # @return [String] 100 | def self.directory 101 | FileUtils.mkdir_p(path) unless File.directory?(path) 102 | 103 | path 104 | end 105 | 106 | # @api private 107 | # @return [String] 108 | def self.path 109 | Dir.home + '/.playa' 110 | end 111 | 112 | end 113 | # :nocov: 114 | end 115 | --------------------------------------------------------------------------------