network_executive 0.0.4 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. data/README.md +53 -24
  2. data/app/assets/fonts/network_executive/league_gothic.css +13 -0
  3. data/app/assets/fonts/network_executive/league_gothic.svg +148 -0
  4. data/app/assets/fonts/network_executive/league_gothic.ttf +0 -0
  5. data/app/assets/fonts/network_executive/league_gothic.woff +0 -0
  6. data/app/assets/javascripts/network_executive/components/iframe_notifier.coffee +14 -0
  7. data/app/assets/javascripts/network_executive/components/namespace.coffee +1 -0
  8. data/app/assets/javascripts/network_executive/components/osd.coffee +65 -0
  9. data/app/assets/javascripts/network_executive/components/photo_player.coffee +55 -0
  10. data/app/assets/javascripts/network_executive/components/postman.coffee +23 -0
  11. data/app/assets/javascripts/network_executive/components/set_top_box.coffee +81 -0
  12. data/app/assets/javascripts/network_executive/components/tweet_player.coffee +95 -0
  13. data/app/assets/javascripts/network_executive/components/you_tube_player.coffee +85 -0
  14. data/app/assets/javascripts/network_executive/slideshow.coffee +6 -0
  15. data/app/assets/javascripts/network_executive/twitter.coffee +7 -0
  16. data/app/assets/javascripts/network_executive/you_tube.coffee +8 -0
  17. data/app/assets/javascripts/network_executive.coffee +10 -0
  18. data/app/assets/stylesheets/network_executive/{gui_components.css → components/gui_components.css} +0 -0
  19. data/app/assets/stylesheets/network_executive/{normalize.css → components/normalize.css} +0 -0
  20. data/app/assets/stylesheets/network_executive/{osd.css → components/osd.css} +6 -34
  21. data/app/assets/stylesheets/network_executive/components/osd_guide.css +43 -0
  22. data/app/assets/stylesheets/network_executive/components/photo_player.css +61 -0
  23. data/app/assets/stylesheets/network_executive/{smpte.css → components/smpte.css} +0 -0
  24. data/app/assets/stylesheets/network_executive/components/tweet_player.css +60 -0
  25. data/app/assets/stylesheets/network_executive/components/you_tube_player.css +12 -0
  26. data/app/assets/stylesheets/network_executive/off_air.css +5 -0
  27. data/app/assets/stylesheets/network_executive/slideshow.css +4 -0
  28. data/app/assets/stylesheets/network_executive/twitter.css +5 -0
  29. data/app/assets/stylesheets/network_executive/you_tube.css +3 -0
  30. data/app/assets/stylesheets/{network_executive/application.css → network_executive.css} +5 -5
  31. data/app/controllers/network_executive/components_controller.rb +4 -0
  32. data/app/controllers/network_executive/guide_controller.rb +41 -0
  33. data/app/controllers/network_executive/programs_controller.rb +9 -0
  34. data/app/helpers/network_executive/network_helper.rb +6 -0
  35. data/app/models/network_executive/channel.rb +15 -14
  36. data/app/models/network_executive/guide.rb +65 -0
  37. data/app/models/network_executive/program.rb +69 -5
  38. data/app/models/network_executive/viewer.rb +4 -0
  39. data/app/programs/network_executive/off_air.rb +18 -0
  40. data/app/views/network_executive/components/slideshow.html.erb +20 -0
  41. data/app/views/network_executive/components/twitter.html.erb +16 -0
  42. data/app/views/network_executive/components/you_tube.html.erb +13 -0
  43. data/app/views/network_executive/guide/index.html.erb +30 -0
  44. data/app/views/network_executive/network/index.html.erb +3 -3
  45. data/app/views/network_executive/programs/off_air.html.erb +13 -0
  46. data/config/routes.rb +7 -1
  47. data/lib/network_executive/channel_schedule.rb +65 -0
  48. data/lib/network_executive/components/photo_player.rb +48 -0
  49. data/lib/network_executive/components/tweet_player.rb +48 -0
  50. data/lib/network_executive/components/you_tube_player.rb +15 -0
  51. data/lib/network_executive/components.rb +3 -0
  52. data/lib/network_executive/engine.rb +29 -3
  53. data/lib/network_executive/off_air_schedule.rb +18 -0
  54. data/lib/network_executive/producer.rb +3 -3
  55. data/lib/network_executive/program_schedule.rb +96 -0
  56. data/lib/network_executive/program_schedule_proxy.rb +55 -0
  57. data/lib/network_executive/scheduled_program.rb +26 -0
  58. data/lib/network_executive/scheduling.rb +15 -21
  59. data/lib/network_executive/time_calculations.rb +10 -0
  60. data/lib/network_executive/version.rb +1 -1
  61. data/lib/network_executive.rb +2 -1
  62. data/network_executive.gemspec +5 -0
  63. data/spec/models/channel_spec.rb +30 -12
  64. data/spec/models/{lineup_spec.rb → guide_spec.rb} +28 -6
  65. data/spec/models/program_spec.rb +46 -3
  66. data/spec/models/viewer_spec.rb +9 -1
  67. data/spec/programs/off_air_spec.rb +9 -0
  68. data/spec/spec_helper.rb +3 -2
  69. data/spec/unit/channel_schedule_spec.rb +67 -0
  70. data/spec/unit/components/photo_player_spec.rb +76 -0
  71. data/spec/unit/components/tweet_player_spec.rb +68 -0
  72. data/spec/unit/components/you_tube_player_spec.rb +17 -0
  73. data/spec/unit/off_air_schedule_spec.rb +28 -0
  74. data/spec/unit/producer_spec.rb +2 -4
  75. data/spec/unit/program_schedule_proxy_spec.rb +102 -0
  76. data/spec/unit/program_schedule_spec.rb +191 -0
  77. data/spec/unit/scheduled_program_spec.rb +57 -0
  78. data/spec/unit/scheduling_spec.rb +61 -72
  79. data/vendor/assets/javascripts/bigtext.js +264 -0
  80. metadata +185 -44
  81. data/app/assets/javascripts/application.js +0 -13
  82. data/app/assets/javascripts/network_executive/osd.js +0 -62
  83. data/app/assets/javascripts/network_executive/set_top_box.js +0 -45
  84. data/app/controllers/network_executive/lineup_controller.rb +0 -11
  85. data/app/models/network_executive/channel_schedule.rb +0 -13
  86. data/app/models/network_executive/lineup.rb +0 -74
  87. data/app/models/network_executive/lineup_range.rb +0 -34
  88. data/app/models/network_executive/program_schedule.rb +0 -148
  89. data/app/views/network_executive/lineup/index.html.erb +0 -26
  90. data/spec/models/channel_schedule_spec.rb +0 -29
  91. data/spec/models/lineup_range_spec.rb +0 -65
  92. data/spec/models/program_schedule_spec.rb +0 -399
@@ -0,0 +1,85 @@
1
+ win = @
2
+
3
+ YT_BASE_URL = '//gdata.youtube.com/feeds/api/users/%USER%/uploads/%TAGS%?v=2&alt=json';
4
+
5
+ # A random sort function
6
+ randomizer = -> 0.5 - Math.random()
7
+
8
+ class @NE.YTPlayer
9
+ constructor : (id, options) ->
10
+ @id = id
11
+ @videos = []
12
+ @options = options || {}
13
+
14
+ win.addEventListener 'load:program', @onLoad, false
15
+
16
+ ready : =>
17
+ @__ready__ = true
18
+
19
+ @loadChannel()
20
+
21
+ url : ->
22
+ url = "#{YT_BASE_URL}"
23
+ user = @options.user
24
+ tags = @options.tags
25
+
26
+ url = url.replace( '%USER%', user )
27
+
28
+ if tags && tags.length > 0
29
+ tags.unshift '-'
30
+
31
+ url.replace '%TAGS%', tags.join('/')
32
+ else
33
+ url.replace '%TAGS%', ''
34
+
35
+ loadChannel : ->
36
+ return unless @__ready__ and @__loaded__
37
+
38
+ xhr = new XMLHttpRequest()
39
+ xhr.addEventListener 'readystatechange', (e) =>
40
+ if xhr.readyState == 4 && xhr.status == 200
41
+ data = JSON.parse( xhr.responseText )
42
+
43
+ @onChannelLoaded data
44
+
45
+ xhr.open 'GET', @url(), true
46
+ xhr.send()
47
+
48
+ onChannelLoaded : (data) ->
49
+ if data.feed.entry
50
+ data.feed.entry.forEach (v) =>
51
+ @videos.push v.media$group.yt$videoid.$t
52
+
53
+ @videos.sort randomizer
54
+
55
+ @createPlayer()
56
+
57
+ createPlayer : ->
58
+ @player = new YT.Player @id,
59
+ playerVars :
60
+ autohide: 1
61
+ events :
62
+ onReady : @onPlayerReady,
63
+ onStateChange : @onPlayerStateChange
64
+
65
+ onPlayerReady : (e) =>
66
+ @player.setLoop true
67
+ @player.setShuffle true
68
+
69
+ @player.loadPlaylist playlist:@videos
70
+
71
+ @player.playVideo()
72
+
73
+ onPlayerStateChange : (e) =>
74
+ if e.data == YT.PlayerState.ENDED
75
+ @player.nextVideo()
76
+
77
+ onLoad : (e) =>
78
+ @__loaded__ = true
79
+
80
+ payload = e.detail.data
81
+
82
+ Object.keys(payload).forEach (k) =>
83
+ @options[k] = payload[k]
84
+
85
+ @loadChannel()
@@ -0,0 +1,6 @@
1
+ #= require ./components/namespace
2
+ #= require ./components/iframe_notifier
3
+ #= require ./components/postman
4
+ #= require ./components/photo_player
5
+
6
+ new NE.PhotoPlayer( 'current_photo', 'title', 'description', 'photographer', 'location' )
@@ -0,0 +1,7 @@
1
+ #= require ./components/namespace
2
+ #= require ./components/iframe_notifier
3
+ #= require ./components/postman
4
+ #= require ./components/tweet_player
5
+ #= require bigtext
6
+
7
+ new NE.TweetPlayer( '#tweet .tweet-text' )
@@ -0,0 +1,8 @@
1
+ #= require ./components/namespace
2
+ #= require ./components/iframe_notifier
3
+ #= require ./components/postman
4
+ #= require ./components/you_tube_player
5
+
6
+ player = new NE.YTPlayer( 'player' )
7
+
8
+ @onYouTubeIframeAPIReady = player.ready
@@ -0,0 +1,10 @@
1
+ #= require ./network_executive/components/namespace
2
+ #= require ./network_executive/components/osd
3
+ #= require ./network_executive/components/set_top_box
4
+ #= require ./network_executive/components/postman
5
+
6
+ paths = @location.pathname.split '/'
7
+ channel = paths[ paths.length - 1]
8
+
9
+ new NE.SetTopBox( channel )
10
+ new NE.OSD()
@@ -9,54 +9,26 @@
9
9
  -moz-transition: opacity .25s ease-in;
10
10
  -transition: opacity .25s ease-in;
11
11
  }
12
+
12
13
  #osd.active {
13
14
  opacity: 1;
14
15
  }
16
+
15
17
  #osd header {
16
- overflow: auto;
18
+ overflow: hidden;
17
19
  }
20
+
18
21
  #osd .network-name {
19
22
  font-size: 5vmin;
20
23
  font-weight: normal;
21
24
  }
22
- #osd a {
23
- color: #fff;
24
- text-decoration: none;
25
- }
25
+
26
26
  .osd-now {
27
27
  float: right;
28
28
  padding-top: .35em;
29
29
  font-size: 2.5vmin;
30
30
  }
31
+
31
32
  .osd-now-date {
32
33
  display: block;
33
- }
34
-
35
- #osd table {
36
- width: 100%;
37
- border-spacing: .25em;
38
- border-collapse: separate;
39
- font-size: 1.5em;
40
- }
41
- #osd td,
42
- #osd th {
43
- height: 1em;
44
- padding: 0 .25em;
45
- overflow: hidden;
46
- text-overflow: ellipsis;
47
- white-space: nowrap;
48
- }
49
- .guide-time {
50
- text-align: left;
51
- }
52
- .guide-channel {
53
- text-align: right;
54
- text-transform: capitalize;
55
- }
56
- .guide-program {
57
- background: rgba(0,0,0,.33);
58
- }
59
- #osd a {
60
- color: #fff;
61
- text-decoration: none;
62
34
  }
@@ -0,0 +1,43 @@
1
+ .guide {
2
+ overflow: hidden;
3
+ }
4
+
5
+ .guide table {
6
+ width: 100%;
7
+ border-spacing: .25em;
8
+ border-collapse: separate;
9
+ font-size: 1.5em;
10
+ }
11
+
12
+ .guide th,
13
+ .guide-program {
14
+ height: 1.2em;
15
+ overflow: hidden;
16
+ text-overflow: ellipsis;
17
+ white-space: nowrap;
18
+ }
19
+
20
+ .guide-time {
21
+ text-align: left;
22
+ }
23
+
24
+ .guide-channel {
25
+ text-align: right;
26
+ text-transform: capitalize;
27
+ }
28
+
29
+ .guide-program-ct {
30
+ float: left;
31
+ }
32
+
33
+ .guide-program {
34
+ display: block;
35
+ margin: 0 .25em;
36
+ padding: 0 .25em;
37
+ background: rgba(0,0,0,.33);
38
+ }
39
+
40
+ .guide a {
41
+ color: #fff;
42
+ text-decoration: none;
43
+ }
@@ -0,0 +1,61 @@
1
+ html,
2
+ body {
3
+ margin: 0;
4
+ padding: 0;
5
+ height: 100%;
6
+ background: #000;
7
+ overflow: hidden;
8
+ }
9
+
10
+ .photo-player {
11
+ margin: 0;
12
+ text-align: center;
13
+ }
14
+
15
+ .current-photo {
16
+ height: 100%;
17
+ max-height: 100%;
18
+ }
19
+
20
+ .caption {
21
+ position: absolute;
22
+ right: 0;
23
+ bottom: 0;
24
+ left: 0;
25
+ padding: 1em;
26
+ background: rgba(0,0,0,0.7);
27
+ color: #fff;
28
+ text-align: left;
29
+ font-family: 'Lato', sans-serif;
30
+ }
31
+
32
+ .caption a {
33
+ display: none;
34
+ }
35
+
36
+ .title {
37
+ margin-bottom: .25em;
38
+ font-size: 1.75em;
39
+ font-weight: bold;
40
+ }
41
+
42
+ .desc {
43
+ font-size: 1.5em;
44
+ max-height: 1.8em;
45
+ overflow: hidden;
46
+ text-overflow: ellipsis;
47
+ white-space: nowrap;
48
+ }
49
+ .desc:not(:empty) {
50
+ padding: .5em 0 .75em;
51
+ }
52
+
53
+ .photog,
54
+ .location {
55
+ display: inline-block;
56
+ font-size: 1.2em;
57
+ }
58
+
59
+ .photog:not(:empty) + .location:not(:empty):before {
60
+ content: ' of ';
61
+ }
@@ -0,0 +1,60 @@
1
+ html,
2
+ body {
3
+ width: 100%;
4
+ height: 100%;
5
+ margin: 0;
6
+ padding: 0;
7
+ overflow: hidden;
8
+ font-family: 'LeagueGothicRegular';
9
+ text-transform: uppercase;
10
+ line-height: .95;
11
+ }
12
+
13
+ .background {
14
+ position: absolute;
15
+ top: 0;
16
+ right: 0;
17
+ bottom: 0;
18
+ left: 0;
19
+ opacity: .3;
20
+ width: 100%;
21
+ height: 100%;
22
+ z-index: -1;
23
+ }
24
+
25
+ .tweet {
26
+ display: -webkit-box;
27
+ display: -moz-box;
28
+ display: -ms-box;
29
+ display: box;
30
+ -webkit-box-pack: center;
31
+ -moz-box-pack: center;
32
+ -ms-box-pack: center;
33
+ box-pack: center;
34
+ -webkit-box-align: center;
35
+ -moz-box-align: center;
36
+ -ms-box-align: center;
37
+ box-align: center;
38
+ -webkit-box-orient: vertical;
39
+ -moz-box-orient: vertical;
40
+ -ms-box-orient: vertical;
41
+ box-orient: vertical;
42
+ height: 100%;
43
+ }
44
+
45
+ .tweet-text {
46
+ width: 430px;
47
+ }
48
+
49
+ .author {
50
+ width: 430px;
51
+ margin-top: .5em;
52
+ text-align: right;
53
+ font-size: 1.75em;
54
+ }
55
+
56
+ .author:before {
57
+ padding-right: .25em;
58
+ content: '--';
59
+ white-space: nowrap;
60
+ }
@@ -0,0 +1,12 @@
1
+ body {
2
+ height: 100%;
3
+ margin: 0;
4
+ padding: 0;
5
+ overflow: hidden;
6
+ background: #000;
7
+ }
8
+
9
+ #player {
10
+ width: 100%;
11
+ height: 100%;
12
+ }
@@ -0,0 +1,5 @@
1
+ /*
2
+ *= require ./components/normalize
3
+ *= require ./components/gui_components
4
+ *= require ./components/smpte
5
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ *= require ./components/normalize
3
+ *= require ./components/photo_player
4
+ */
@@ -0,0 +1,5 @@
1
+ /*
2
+ *= require ./components/normalize
3
+ *= require ./components/tweet_player
4
+ *= require network_executive/league_gothic
5
+ */
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require ./components/you_tube_player
3
+ */
@@ -8,9 +8,9 @@
8
8
  * You're free to add application-wide styles to this file and they'll appear at the top of the
9
9
  * compiled file, but it's generally better to create a new file per style scope.
10
10
  *
11
- *= require ./normalize
12
- *= require ./gui_components
13
- *= require ./smpte
14
- *= require ./osd
15
- *= require_self
11
+ *= require ./network_executive/components/normalize
12
+ *= require ./network_executive/components/gui_components
13
+ *= require ./network_executive/components/smpte
14
+ *= require ./network_executive/components/osd
15
+ *= require ./network_executive/components/osd_guide
16
16
  */
@@ -0,0 +1,4 @@
1
+ module NetworkExecutive
2
+ class ComponentsController < NetworkExecutive::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,41 @@
1
+ module NetworkExecutive
2
+ class GuideController < NetworkExecutive::ApplicationController
3
+
4
+ respond_to :html, :json
5
+
6
+ cattr_accessor :index_cached
7
+
8
+ def index
9
+ if stale_cache?
10
+ self.class.index_cached = true
11
+
12
+ @guide = Guide.new
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ # Only 304 if the cache is fresh *OR* we haven't cached the page yet.
19
+ # This allows server reboots to regenerate the content in the case
20
+ # of an error.
21
+ #
22
+ # NOTE: It is important to note the order of the arguments. The call to
23
+ # Rails` `stale?` method does magical things to the response, so its
24
+ # important that it comes last. If the custom logic indicates that the
25
+ # page should not be cached, then we don't want the Rails method to be
26
+ # called and magically send an unwanted 304.
27
+ def stale_cache?
28
+ !self.class.index_cached || stale?( caching_params )
29
+ end
30
+
31
+ def caching_params
32
+ current_time_slot = Time.now.floor( Guide::Interval.minutes )
33
+
34
+ {
35
+ last_modified: current_time_slot,
36
+ etag: current_time_slot.to_i
37
+ }
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ module NetworkExecutive
2
+ class ProgramsController < NetworkExecutive::ApplicationController
3
+
4
+ def show
5
+ render "network_executive/programs/#{params[:program_name]}"
6
+ end
7
+
8
+ end
9
+ end
@@ -1,7 +1,13 @@
1
1
  module NetworkExecutive
2
2
  module NetworkHelper
3
+
3
4
  def network_name
4
5
  NetworkExecutive.config.name
5
6
  end
7
+
8
+ def guide_header_width
9
+ '%.3f' % (1 / (@guide.times.size.to_f + 1) * 100)
10
+ end
11
+
6
12
  end
7
13
  end
@@ -3,30 +3,31 @@ require 'network_executive/scheduling'
3
3
 
4
4
  module NetworkExecutive
5
5
  class Channel < EventMachine::Channel
6
- include Scheduling
7
-
8
- # Example
9
- # every :monday, play:'morning_show'
10
- # every :day, play:'stoplight', for_the_first:'15mins', of:'each hour', ending_at:'11am'
11
- # every :day, play:'tweets', for_the_last: '15mins', of:'every hour'
12
- # every :day, play:'sales', for_the_first:'15mins', of:'each hour', between:'2pm and 5pm'
13
- # every :friday, play:'lunch_menu', between:'11am and 13pm', commercial_free:true
6
+ include NetworkExecutive::Scheduling
14
7
 
15
8
  def name
16
9
  self.class.name.demodulize.underscore
17
10
  end
11
+ alias_method :to_s, :name
18
12
 
19
13
  def display_name
20
14
  name.gsub %r{_}, ' '
21
15
  end
22
- alias_method :to_s, :display_name
23
-
24
- def show( scheduled_program )
25
- program = Program.find_by_name scheduled_program.program_name
26
16
 
27
- raise ProgramNotFoundError.new("#{scheduled_program.program_name} is not a registered channel") unless program
17
+ def play( program )
18
+ if program.occurs_at?( Time.now.change(sec:0) )
19
+ program.play do |msg|
20
+ push msg
21
+ end
22
+ else
23
+ program.update do |msg|
24
+ push msg
25
+ end
26
+ end
27
+ end
28
28
 
29
- push program.play
29
+ def play_whats_on( &block )
30
+ whats_on?.play( &block )
30
31
  end
31
32
 
32
33
  class << self
@@ -0,0 +1,65 @@
1
+ # Represents the currently scheduled programs for all channels in a Network
2
+ # for a given time range.
3
+ module NetworkExecutive
4
+ # TODO: Why does this subclass Hash?
5
+ # TODO: This is an ugly data structure. Refactor.
6
+ class Guide < Hash
7
+
8
+ Interval = 15 # In minutes.
9
+ Range = 1.5 # In hours.
10
+
11
+ attr_accessor :start_time, :stop_time
12
+
13
+ def initialize( start = nil, stop = nil )
14
+ self.start_time = start || Time.now
15
+ self.stop_time = stop || Time.now + Range.hours
16
+
17
+ self[:channels] = generate
18
+ end
19
+
20
+ def generate
21
+ with_each_channel do |channel, guide|
22
+ programs = channel.whats_on_between? start_time, stop_time, Interval.minutes
23
+
24
+ guide << {
25
+ channel: channel,
26
+ programs: programs
27
+ }
28
+ end
29
+ end
30
+
31
+ def start_time=( time )
32
+ @start_time = time.floor( Interval.minutes )
33
+ end
34
+
35
+ def stop_time=( time )
36
+ @stop_time = time.floor( Interval.minutes )
37
+ end
38
+
39
+ # TODO: Add test
40
+ def times
41
+ @times ||= begin
42
+ cursor = start_time.dup
43
+
44
+ times = []
45
+
46
+ while cursor < stop_time do
47
+ times << cursor
48
+
49
+ cursor += Interval.minutes
50
+ end
51
+
52
+ times
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ # TODO: Decouple
59
+ def with_each_channel
60
+ Network.channels.each_with_object([]) do |channel, lu|
61
+ yield channel, lu
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,5 +1,7 @@
1
1
  require 'multi_json'
2
2
 
3
+ require 'network_executive/network'
4
+
3
5
  module NetworkExecutive
4
6
  class Program
5
7
 
@@ -7,19 +9,81 @@ module NetworkExecutive
7
9
  self.class.name.demodulize.underscore
8
10
  end
9
11
 
12
+ def display_name
13
+ name.titleize
14
+ end
15
+
10
16
  def url
11
17
  ''
12
18
  end
13
19
 
14
- def as_json
20
+ # Will the embedded page be responsible for keeping the content fresh?
21
+ #
22
+ # * `:auto` - The page will be reloaded the page every minute.
23
+ # * `FALSE` - The page will not be reloaded
24
+ #
25
+ # Defaults to `:auto`
26
+ def refresh
27
+ :auto
28
+ end
29
+
30
+ # A Hash containing data to pass to the embedded page via postMessage
31
+ # once the IFRAME has indicated that it has loaded.
32
+ def onload
33
+ {}
34
+ end
35
+
36
+ # A Hash containing data to pass to the embedded page via postMessage
37
+ # whenever a program update is issued.
38
+ def onupdate
39
+ {}
40
+ end
41
+
42
+ # A Hash containing data to pass to the embedded page via postMessage
43
+ # whenever a program is initially shown
44
+ def onshow
15
45
  {
16
- name: name,
17
- url: url
46
+ name: name,
47
+ url: url,
48
+ onLoad: onload,
49
+ refresh: refresh
18
50
  }
19
51
  end
20
52
 
21
- def play
22
- MultiJson.encode( as_json )
53
+ def play( &block )
54
+ Rails.logger.info %Q[Started playing "#{display_name}" at #{Time.now} (refresh: #{refresh})]
55
+
56
+ defer = EM::DefaultDeferrable.new
57
+
58
+ defer.callback( &block )
59
+
60
+ EM.defer do
61
+ payload = onshow.merge( event:'show:program' )
62
+ json = MultiJson.encode( payload )
63
+
64
+ defer.succeed json
65
+ end
66
+
67
+ defer
68
+ end
69
+
70
+ def update( &block )
71
+ return play( &block ) if refresh == :auto
72
+
73
+ Rails.logger.info %Q[Started updating "#{display_name}" at #{Time.now}]
74
+
75
+ defer = EM::DefaultDeferrable.new
76
+
77
+ defer.callback( &block )
78
+
79
+ EM.defer do
80
+ payload = onupdate.merge( event:'update:program' )
81
+ json = MultiJson.encode( payload )
82
+
83
+ defer.succeed json
84
+ end
85
+
86
+ defer
23
87
  end
24
88
 
25
89
  class << self