diff --git a/pandora/0xdb.jsonc b/pandora/0xdb.jsonc index 56c728881..3fb3d6c76 100644 --- a/pandora/0xdb.jsonc +++ b/pandora/0xdb.jsonc @@ -581,6 +581,9 @@ {"id": "size", "admin": true}, {"id": "pixels"} ], + "tv": { + "showLogo": false + }, "user": { "level": "guest", "ui": { diff --git a/pandora/tv/models.py b/pandora/tv/models.py index b2ba0d896..38d70a9c2 100644 --- a/pandora/tv/models.py +++ b/pandora/tv/models.py @@ -5,23 +5,27 @@ from datetime import datetime, timedelta from random import randint from django.db import models +from django.db.models import Max class Channel(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) - run = models.IntegerField(default=0) + run = models.IntegerField(default=0) list = models.ForeignKey('itemlist.List', related_name='channel') + def __unicode__(self): + return u"%s %s" % (self.list, self.run) + def json(self, user): now = datetime.now() items = self.list.get_items().filter(rendered=True) if items.count() == 0: - return + return {} program = self.program.order_by('-start') - while program.count() < 2 or program[0].end < now: + while program.count() < 1 or program[0].end < now: not_played = items.exclude(program__in=self.program.filter(run=self.run)) not_played_count = not_played.count() if not_played_count == 0: @@ -31,24 +35,18 @@ class Channel(models.Model): not_played_count = not_played.count() item = not_played[randint(0, not_played_count-1)] if program.count() > 0: - start = program[0].end + start = program.aggregate(Max('end'))['end__max'] else: start = now - end = start + timedelta(seconds=item.get_json()['duration']) p = Program() - p.item=item - p.run=self.run - p.start=start - p.end=end + p.item = item + p.run = self.run + p.start = start + p.end = start + timedelta(seconds=item.get_json()['duration']) p.channel = self p.save() program = self.program.order_by('-start') - print program.count(), now, p.start, p.end - current = program[1] - return { - 'current': current.json(user, now), - 'next': program[0].json(user) - } + return program[0].json(user, now) class Program(models.Model): created = models.DateTimeField(auto_now_add=True) @@ -66,11 +64,13 @@ class Program(models.Model): item_json = self.item.get_json() r = { 'item': self.item.itemId, - 'durations': item_json['durations'], - 'parts': item_json['parts'], } + for key in ('title', 'director', 'year', 'durations', 'parts', 'rightslevel'): + r[key] = item_json.get(key, '') r['layers'] = self.item.get_layers(user) if current: - r['currentTime'] = (current - self.start).total_seconds() - + #requires python2.7 + #r['position'] = (current - self.start).total_seconds() + td = current - self.start + r['position'] = td.seconds + td.days * 24 * 3600 + float(td.microseconds)/10**6 return r diff --git a/pandora/tv/views.py b/pandora/tv/views.py index 055de83f3..fc283cb04 100644 --- a/pandora/tv/views.py +++ b/pandora/tv/views.py @@ -4,18 +4,19 @@ from __future__ import division import models from ox.utils import json -from ox.django.decorators import login_required_json from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response from itemlist.views import get_list_or_404_json from api.actions import actions -@login_required_json def tv(request): data = json.loads(request.POST['data']) list = get_list_or_404_json(data['list']) - channel, created = models.Channel.objects.get_or_create(list=list) - response = json_response(status=200, text='created') - response['data'] = channel.json(request.user) + if list.accessible(request.user): + channel, created = models.Channel.objects.get_or_create(list=list) + response = json_response(status=200, text='created') + response['data'] = channel.json(request.user) + else: + response = json_response(status=404, text='list not found') return render_to_json_response(response) actions.register(tv, cache=False) diff --git a/static/js/pandora/URL.js b/static/js/pandora/URL.js index e5ba9ec33..2c3335eca 100644 --- a/static/js/pandora/URL.js +++ b/static/js/pandora/URL.js @@ -226,7 +226,7 @@ pandora.URL = (function() { pages: [ 'about', 'api', 'contact', 'faq', 'help', 'home', 'news', 'preferences', 'rights', 'signin', 'signout', 'signup', - 'software', 'terms', 'tour' + 'software', 'terms', 'tour', 'tv' ], sortKeys: sortKeys, spanType: spanType, diff --git a/static/js/pandora/appPanel.js b/static/js/pandora/appPanel.js index 00381225b..e31bf2a69 100644 --- a/static/js/pandora/appPanel.js +++ b/static/js/pandora/appPanel.js @@ -68,6 +68,10 @@ pandora.ui.appPanel = function() { } else { pandora.ui.accountSignoutDialog().open(); } + } else if (page == 'tv') { + pandora.$ui.tv = pandora.ui.tv()[ + !pandora.$ui.appPanel ? 'showScreen' : 'fadeInScreen' + ](); } } return that; diff --git a/static/js/pandora/item.js b/static/js/pandora/item.js index 97e46d9e8..fcf4df098 100644 --- a/static/js/pandora/item.js +++ b/static/js/pandora/item.js @@ -39,47 +39,7 @@ pandora.ui.item = function() { if (['video', 'timeline'].indexOf(pandora.user.ui.itemView) > -1) { // fixme: layers have value, subtitles has text? - var subtitles = result.data.layers.subtitles - ? result.data.layers.subtitles.map(function(subtitle) { - return {'in': subtitle['in'], out: subtitle.out, text: subtitle.value}; - }) - : [], - canPlayClips = pandora.site.capabilities.canPlayClips[pandora.user.level] >= result.data.rightslevel, - canPlayVideo = pandora.site.capabilities.canPlayVideo[pandora.user.level] >= result.data.rightslevel, - censored = canPlayVideo ? [] - : canPlayClips ? ( - subtitles.length - ? Ox.merge( - subtitles.map(function(subtitle, i) { - return { - 'in': i == 0 ? 0 : subtitles[i - 1].out, - out: subtitle['in'] - }; - }), - [{'in': Ox.last(subtitles).out, out: result.data.duration}] - ) - : Ox.range(0, result.data.duration - 5, 60).map(function(position) { - return { - 'in': position + 5, - out: Math.min(position + 60, result.data.duration) - }; - }) - ) - : [{'in': 0, out: result.data.duration}], - layers = [], - video = {}; - - pandora.site.layers.forEach(function(layer, i) { - layers[i] = Ox.extend({}, layer, {items: result.data.layers[layer.id]}); - }); - pandora.site.video.resolutions.forEach(function(resolution) { - video[resolution] = Ox.range(result.data.parts).map(function(i) { - var part = (i + 1), - prefix = pandora.site.site.videoprefix.replace('PART', part); - return prefix + '/' + pandora.user.ui.item + '/' - + resolution + 'p' + part + '.' + pandora.user.videoFormat; - }); - }); + var videoOptions = pandora.getVideoOptions(result.data); } if (!result.data.rendered && [ @@ -160,7 +120,7 @@ pandora.ui.item = function() { } else if (pandora.user.ui.itemView == 'video') { pandora.$ui.contentPanel.replaceElement(1, pandora.$ui.player = Ox.VideoPanelPlayer({ annotationsSize: pandora.user.ui.annotationsSize, - censored: censored, + censored: videoOptions.censored, cuts: result.data.cuts || [], duration: result.data.duration, find: pandora.user.ui.itemFind.conditions[0] @@ -176,10 +136,10 @@ pandora.ui.item = function() { scaleToFill: pandora.user.ui.videoScale == 'fill', showAnnotations: pandora.user.ui.showAnnotations, showTimeline: pandora.user.ui.showTimeline, - subtitles: subtitles, + subtitles: videoOptions.subtitles, tooltips: true, timeline: '/' + pandora.user.ui.item + '/timeline16p.png', - video: video, + video: videoOptions.video, volume: pandora.user.ui.videoVolume, width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1 }).bindEvent({ diff --git a/static/js/pandora/tv.js b/static/js/pandora/tv.js new file mode 100644 index 000000000..f90ae7941 --- /dev/null +++ b/static/js/pandora/tv.js @@ -0,0 +1,77 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript + +'use strict'; + +pandora.ui.tv = function() { + + var that = Ox.Element() + .addClass('OxScreen') + .css({ + position: 'absolute', + width: '100%', + height: '100%', + opacity: 0, + zIndex: 1000 + }), + $player; + + function play() { + pandora.api.tv({ + list: pandora.user.ui._list + }, function(result) { + var videoOptions = pandora.getVideoOptions(result.data); + $player && player.remove(); + $player = Ox.VideoPlayer({ + censored: videoOptions.censored, + controlsBottom: ['volume', 'scale', 'timeline', 'position', 'resolution'], + controlsTop: ['close', 'title'], + duration: result.data.duration, + fullscreen: true, + logo: pandora.site.tv.showLogo ? '/static/png/logo256.png' : '', + position: result.data.position, + scaleToFill: pandora.user.ui.videoScale == 'fill', + subtitles: videoOptions.subtitles, + tooltips: true, + timeline: '/' + result.data.item + '/timeline16p.png', + title: pandora.site.site.name + ' — ' + ( + pandora.user.ui._list + ? pandora.user.ui._list + : 'All ' + pandora.site.itemName.plural + ) + ' — ' + + result.data.title + + ' (' + result.data.director.join(', ') + ') ' + + result.data.year, + video: videoOptions.video, + volume: pandora.user.ui.videoVolume + }) + .bindEvent({ + close: function() { + + }, + ended: play + }) + .appendTo(that); + }); + } + + that.fadeInScreen = function() { + that.appendTo(Ox.UI.$body).animate({opacity: 1}, 500); + play(); + }; + + that.fadeOutScreen = function() { + + }; + + that.hideScreen = function() { + + }; + + that.showScreen = function() { + that.css({opacity: 1}).appendTo(Ox.UI.$body); + play(); + }; + + return that; + +} \ No newline at end of file diff --git a/static/js/pandora/utils.js b/static/js/pandora/utils.js index 7dc4dc4be..e5df97934 100644 --- a/static/js/pandora/utils.js +++ b/static/js/pandora/utils.js @@ -722,6 +722,47 @@ pandora.getSortOperator = function(key) { ) > -1 ? '+' : '-'; }; +pandora.getVideoOptions = function(data) { + var canPlayClips = pandora.site.capabilities.canPlayClips[pandora.user.level] >= data.rightslevel, + canPlayVideo = pandora.site.capabilities.canPlayVideo[pandora.user.level] >= data.rightslevel, + options = {}; + options.subtitles = data.layers.subtitles + ? data.layers.subtitles.map(function(subtitle) { + return {'in': subtitle['in'], out: subtitle.out, text: subtitle.value}; + }) + : []; + options.censored = canPlayVideo ? [] + : canPlayClips ? ( + options.subtitles.length + ? Ox.merge( + options.subtitles.map(function(subtitle, i) { + return { + 'in': i == 0 ? 0 : options.subtitles[i - 1].out, + out: subtitle['in'] + }; + }), + [{'in': Ox.last(options.subtitles).out, out: data.duration}] + ) + : Ox.range(0, data.duration - 5, 60).map(function(position) { + return { + 'in': position + 5, + out: Math.min(position + 60, data.duration) + }; + }) + ) + : [{'in': 0, out: data.duration}]; + options.video = {}; + pandora.site.video.resolutions.forEach(function(resolution) { + options.video[resolution] = Ox.range(data.parts).map(function(i) { + var part = (i + 1), + prefix = pandora.site.site.videoprefix.replace('PART', part); // fixme: '{part}' would be more consistent + return prefix + '/' + (data.item || pandora.user.ui.item) + '/' + + resolution + 'p' + part + '.' + pandora.user.videoFormat; + }); + }); + return options; +}; + pandora.getVideoPartsAndPoints = function(durations, points) { var parts = durations.length, offsets = Ox.range(parts).map(function(i) {