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.
- data/README.md +53 -24
- data/app/assets/fonts/network_executive/league_gothic.css +13 -0
- data/app/assets/fonts/network_executive/league_gothic.svg +148 -0
- data/app/assets/fonts/network_executive/league_gothic.ttf +0 -0
- data/app/assets/fonts/network_executive/league_gothic.woff +0 -0
- data/app/assets/javascripts/network_executive/components/iframe_notifier.coffee +14 -0
- data/app/assets/javascripts/network_executive/components/namespace.coffee +1 -0
- data/app/assets/javascripts/network_executive/components/osd.coffee +65 -0
- data/app/assets/javascripts/network_executive/components/photo_player.coffee +55 -0
- data/app/assets/javascripts/network_executive/components/postman.coffee +23 -0
- data/app/assets/javascripts/network_executive/components/set_top_box.coffee +81 -0
- data/app/assets/javascripts/network_executive/components/tweet_player.coffee +95 -0
- data/app/assets/javascripts/network_executive/components/you_tube_player.coffee +85 -0
- data/app/assets/javascripts/network_executive/slideshow.coffee +6 -0
- data/app/assets/javascripts/network_executive/twitter.coffee +7 -0
- data/app/assets/javascripts/network_executive/you_tube.coffee +8 -0
- data/app/assets/javascripts/network_executive.coffee +10 -0
- data/app/assets/stylesheets/network_executive/{gui_components.css → components/gui_components.css} +0 -0
- data/app/assets/stylesheets/network_executive/{normalize.css → components/normalize.css} +0 -0
- data/app/assets/stylesheets/network_executive/{osd.css → components/osd.css} +6 -34
- data/app/assets/stylesheets/network_executive/components/osd_guide.css +43 -0
- data/app/assets/stylesheets/network_executive/components/photo_player.css +61 -0
- data/app/assets/stylesheets/network_executive/{smpte.css → components/smpte.css} +0 -0
- data/app/assets/stylesheets/network_executive/components/tweet_player.css +60 -0
- data/app/assets/stylesheets/network_executive/components/you_tube_player.css +12 -0
- data/app/assets/stylesheets/network_executive/off_air.css +5 -0
- data/app/assets/stylesheets/network_executive/slideshow.css +4 -0
- data/app/assets/stylesheets/network_executive/twitter.css +5 -0
- data/app/assets/stylesheets/network_executive/you_tube.css +3 -0
- data/app/assets/stylesheets/{network_executive/application.css → network_executive.css} +5 -5
- data/app/controllers/network_executive/components_controller.rb +4 -0
- data/app/controllers/network_executive/guide_controller.rb +41 -0
- data/app/controllers/network_executive/programs_controller.rb +9 -0
- data/app/helpers/network_executive/network_helper.rb +6 -0
- data/app/models/network_executive/channel.rb +15 -14
- data/app/models/network_executive/guide.rb +65 -0
- data/app/models/network_executive/program.rb +69 -5
- data/app/models/network_executive/viewer.rb +4 -0
- data/app/programs/network_executive/off_air.rb +18 -0
- data/app/views/network_executive/components/slideshow.html.erb +20 -0
- data/app/views/network_executive/components/twitter.html.erb +16 -0
- data/app/views/network_executive/components/you_tube.html.erb +13 -0
- data/app/views/network_executive/guide/index.html.erb +30 -0
- data/app/views/network_executive/network/index.html.erb +3 -3
- data/app/views/network_executive/programs/off_air.html.erb +13 -0
- data/config/routes.rb +7 -1
- data/lib/network_executive/channel_schedule.rb +65 -0
- data/lib/network_executive/components/photo_player.rb +48 -0
- data/lib/network_executive/components/tweet_player.rb +48 -0
- data/lib/network_executive/components/you_tube_player.rb +15 -0
- data/lib/network_executive/components.rb +3 -0
- data/lib/network_executive/engine.rb +29 -3
- data/lib/network_executive/off_air_schedule.rb +18 -0
- data/lib/network_executive/producer.rb +3 -3
- data/lib/network_executive/program_schedule.rb +96 -0
- data/lib/network_executive/program_schedule_proxy.rb +55 -0
- data/lib/network_executive/scheduled_program.rb +26 -0
- data/lib/network_executive/scheduling.rb +15 -21
- data/lib/network_executive/time_calculations.rb +10 -0
- data/lib/network_executive/version.rb +1 -1
- data/lib/network_executive.rb +2 -1
- data/network_executive.gemspec +5 -0
- data/spec/models/channel_spec.rb +30 -12
- data/spec/models/{lineup_spec.rb → guide_spec.rb} +28 -6
- data/spec/models/program_spec.rb +46 -3
- data/spec/models/viewer_spec.rb +9 -1
- data/spec/programs/off_air_spec.rb +9 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/unit/channel_schedule_spec.rb +67 -0
- data/spec/unit/components/photo_player_spec.rb +76 -0
- data/spec/unit/components/tweet_player_spec.rb +68 -0
- data/spec/unit/components/you_tube_player_spec.rb +17 -0
- data/spec/unit/off_air_schedule_spec.rb +28 -0
- data/spec/unit/producer_spec.rb +2 -4
- data/spec/unit/program_schedule_proxy_spec.rb +102 -0
- data/spec/unit/program_schedule_spec.rb +191 -0
- data/spec/unit/scheduled_program_spec.rb +57 -0
- data/spec/unit/scheduling_spec.rb +61 -72
- data/vendor/assets/javascripts/bigtext.js +264 -0
- metadata +185 -44
- data/app/assets/javascripts/application.js +0 -13
- data/app/assets/javascripts/network_executive/osd.js +0 -62
- data/app/assets/javascripts/network_executive/set_top_box.js +0 -45
- data/app/controllers/network_executive/lineup_controller.rb +0 -11
- data/app/models/network_executive/channel_schedule.rb +0 -13
- data/app/models/network_executive/lineup.rb +0 -74
- data/app/models/network_executive/lineup_range.rb +0 -34
- data/app/models/network_executive/program_schedule.rb +0 -148
- data/app/views/network_executive/lineup/index.html.erb +0 -26
- data/spec/models/channel_schedule_spec.rb +0 -29
- data/spec/models/lineup_range_spec.rb +0 -65
- 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,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()
|
data/app/assets/stylesheets/network_executive/{gui_components.css → components/gui_components.css}
RENAMED
|
File without changes
|
|
File without changes
|
|
@@ -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:
|
|
18
|
+
overflow: hidden;
|
|
17
19
|
}
|
|
20
|
+
|
|
18
21
|
#osd .network-name {
|
|
19
22
|
font-size: 5vmin;
|
|
20
23
|
font-weight: normal;
|
|
21
24
|
}
|
|
22
|
-
|
|
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
|
+
}
|
|
File without changes
|
|
@@ -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
|
+
}
|
|
@@ -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
|
-
*=
|
|
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,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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
17
|
-
url:
|
|
46
|
+
name: name,
|
|
47
|
+
url: url,
|
|
48
|
+
onLoad: onload,
|
|
49
|
+
refresh: refresh
|
|
18
50
|
}
|
|
19
51
|
end
|
|
20
52
|
|
|
21
|
-
def play
|
|
22
|
-
|
|
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
|