12 changed files with 385 additions and 5 deletions
@ -0,0 +1,24 @@ |
|||||
|
from urllib.parse import urlparse |
||||
|
import datetime |
||||
|
|
||||
|
from django import forms |
||||
|
|
||||
|
from player.models import Playlist, Link |
||||
|
|
||||
|
class PlaylistForm(forms.ModelForm): |
||||
|
class Meta: |
||||
|
model = Playlist |
||||
|
fields = ['name'] |
||||
|
|
||||
|
def is_valid(self): |
||||
|
self.instance.date = datetime.datetime.now() |
||||
|
return super().is_valid() |
||||
|
|
||||
|
class LinkForm(forms.Form): |
||||
|
url = forms.URLField(label="URL de la piste à ajouter") |
||||
|
|
||||
|
def get_token(self): |
||||
|
p=urlparse(self.cleaned_data['url']) |
||||
|
print(p.query) |
||||
|
return [i for i in p.query.split('&') if i and i[0]=='v'][0].split('=')[-1] |
||||
|
|
||||
@ -0,0 +1,29 @@ |
|||||
|
# Generated by Django 2.0.3 on 2018-03-24 22:40 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('player', '0001_initial'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.RemoveField( |
||||
|
model_name='link', |
||||
|
name='url', |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='link', |
||||
|
name='token', |
||||
|
field=models.CharField(default='', max_length=200, verbose_name='Token'), |
||||
|
preserve_default=False, |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='playlist', |
||||
|
name='name', |
||||
|
field=models.CharField(default='Nom', max_length=255, verbose_name='Nom de la playlist'), |
||||
|
preserve_default=False, |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,30 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div class="row"> |
||||
|
<div class="col-md-6 text-justify"> |
||||
|
<h1>Live Share</h1> |
||||
|
<br/> |
||||
|
<p>Bienvenue sur Live Share. Vous pouvez créer des playlist youtube anonymes, collaboratives et éphémères !</p> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
<a class="btn btn-success btn-lg" type='button' href="{% url 'player:new' %}"> |
||||
|
<i class="fa fa-plus"></i> Nouvelle playlist |
||||
|
</a> |
||||
|
<br/> |
||||
|
<br/> |
||||
|
</div> |
||||
|
<div class="col-md-6 text-justify"> |
||||
|
<h1>Diffusions en cours</h1> |
||||
|
<br/> |
||||
|
{% for l in lists %} |
||||
|
<a class="card bg-secondary text-white" href="{{l.get_absolute_url}}"> |
||||
|
<div class="card-body"> |
||||
|
<i class="far fa-play-circle"></i> |
||||
|
{{l.name}} |
||||
|
</div> |
||||
|
</a> |
||||
|
<br/> |
||||
|
{% endfor %} |
||||
|
</div> |
||||
|
{% endblock %} |
||||
@ -0,0 +1,164 @@ |
|||||
|
{% extends "base.html" %} |
||||
|
{% load bootstrap4 %} |
||||
|
|
||||
|
{% block title %} - {{playlist.name}}{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div class="row"> |
||||
|
<div class="col-md-3"> |
||||
|
<h1>{{playlist.name}}</h1> |
||||
|
</div> |
||||
|
<div class="col-md-3"> |
||||
|
<a class="btn btn-success btn-sm" type='button' href=""> |
||||
|
<i class="fa fa-edit"></i> Changer le nom |
||||
|
</a> |
||||
|
</div> |
||||
|
<div class="col-md-6"> |
||||
|
Lien de partage : <input class="input-monospace" value="{{request.get_host}}{{playlist.get_absolute_url}}" type="text" readonly=""> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row" style="height:100%"> |
||||
|
<div class="col-md-6"> |
||||
|
<h2>Lecture</h2> |
||||
|
<!-- 1. The <iframe> (and video player) will replace this <div> tag. --> |
||||
|
<div id="player" style="max-width:100%"></div> |
||||
|
<form onSubmit="return addLink();" class="form" id="add_link_form"> |
||||
|
{% csrf_token %} |
||||
|
{% bootstrap_form form %} |
||||
|
<button class="btn btn-outline-primary"><i class="fa fa-plus"></i> Ajouter</button> |
||||
|
</form> |
||||
|
<script> |
||||
|
var tracks = [ |
||||
|
{% for link in playlist.link_set.all %} |
||||
|
"{{link.token}}", |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
// 2. This code loads the IFrame Player API code asynchronously. |
||||
|
var tag = document.createElement('script'); |
||||
|
var current_link = -1; |
||||
|
tag.src = "https://www.youtube.com/iframe_api"; |
||||
|
var firstScriptTag = document.getElementsByTagName('script')[0]; |
||||
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); |
||||
|
|
||||
|
// 3. This function creates an <iframe> (and YouTube player) |
||||
|
// after the API code downloads. |
||||
|
var player; |
||||
|
function onYouTubeIframeAPIReady() { |
||||
|
player = new YT.Player('player', { |
||||
|
height: '390', |
||||
|
width: '640', |
||||
|
events: { |
||||
|
'onReady': onPlayerReady, |
||||
|
'onStateChange': onPlayerStateChange |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 4. The API will call this function when the video player is ready. |
||||
|
function onPlayerReady(event) { |
||||
|
console.log(current_link); |
||||
|
console.log(tracks.length); |
||||
|
if(tracks.length > 0 && current_link < (tracks.length - 1)) { |
||||
|
current_link += 1 |
||||
|
player.loadVideoById(tracks[current_link]); |
||||
|
event.target.playVideo(); |
||||
|
} |
||||
|
setInterval(loadLinks, 5000); |
||||
|
} |
||||
|
|
||||
|
function onPlayerStateChange(event) { |
||||
|
if (event.data == YT.PlayerState.ENDED) { |
||||
|
onPlayerReady(); |
||||
|
} |
||||
|
} |
||||
|
function stopVideo() { |
||||
|
player.stopVideo(); |
||||
|
} |
||||
|
function addLink() { |
||||
|
var form = $('#add_link_form'); |
||||
|
$.ajax({ |
||||
|
type: "post", |
||||
|
url: "{% url 'player:add' playlist.get_token %}", |
||||
|
data: form.serialize(), |
||||
|
async: true, |
||||
|
success: loadLinks, |
||||
|
}) |
||||
|
$('#id_url').val(''); |
||||
|
return false; |
||||
|
} |
||||
|
function updateLinks(data) { |
||||
|
var links = document.getElementById("links"); |
||||
|
while (links.firstChild) { |
||||
|
links.removeChild(links.firstChild); |
||||
|
} |
||||
|
var model = document.getElementById('link_template'); |
||||
|
tracks = []; |
||||
|
for (var i=0; i<data.length; i++) |
||||
|
{ |
||||
|
tracks.push(data[i].fields.token); |
||||
|
var card = model.cloneNode(true); |
||||
|
card.style.display = 'block'; |
||||
|
card.id = 'link_' + data[i].fields.token; |
||||
|
card.getElementsByClassName('link_name')[0].innerHTML = data[i].fields.token; |
||||
|
links.appendChild(card); |
||||
|
links.append(document.createElement('br')); |
||||
|
} |
||||
|
if (current_link < 0) { |
||||
|
onPlayerReady(); |
||||
|
} |
||||
|
} |
||||
|
function loadLinks() { |
||||
|
$.ajax({ |
||||
|
type: "get", |
||||
|
url: "{% url 'player:list' playlist.get_token %}", |
||||
|
async: true, |
||||
|
success: updateLinks |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
</div> |
||||
|
<div class="col-md-6" style="height:100%; overflow-y:scroll;"> |
||||
|
<h2>Pistes à venir</h2> |
||||
|
<div id="links"> |
||||
|
{% for link in playlist.link_set.all %} |
||||
|
<div class="card bg-secondary text-white" href="{{l.get_absolute_url}}" id="link_{{link.token}}"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row container"> |
||||
|
<div class="col-md-6"> |
||||
|
<i class="far fa-play-circle"></i> |
||||
|
{{link.token}} |
||||
|
</div> |
||||
|
<div class="col-md-3"> |
||||
|
</div> |
||||
|
<div class="col-md-3 btn-group"> |
||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-up"></i></a> |
||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-down"></i></a> |
||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-trash"></i></a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br/> |
||||
|
{% endfor %} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style="display:none;" class="card bg-secondary text-white" href="{{l.get_absolute_url}}" id="link_template"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row container"> |
||||
|
<div class="col-md-6"> |
||||
|
<i class="far fa-play-circle"></i> |
||||
|
<span class="link_name"></span> |
||||
|
</div> |
||||
|
<div class="col-md-3"> |
||||
|
</div> |
||||
|
<div class="col-md-3 btn-group"> |
||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-up"></i></a> |
||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-down"></i></a> |
||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-trash"></i></a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{% endblock %} |
||||
@ -0,0 +1,12 @@ |
|||||
|
from django.urls import path |
||||
|
from . import views |
||||
|
|
||||
|
app_name = "player" |
||||
|
urlpatterns = [ |
||||
|
path('<int:token>', views.playlist, name='playlist'), |
||||
|
path('remove/<int:pk>', views.remove_link, name='remove-link'), |
||||
|
path('<int:token>/add', views.add_link, name='add'), |
||||
|
path('<int:token>/list', views.get_list, name='list'), |
||||
|
path('new', views.new_playlist, name='new'), |
||||
|
path('', views.all_playlist, name='all'), |
||||
|
] |
||||
@ -1,3 +1,65 @@ |
|||||
from django.shortcuts import render |
import datetime |
||||
|
|
||||
# Create your views here. |
from django.shortcuts import render, get_object_or_404, redirect |
||||
|
from django.http import HttpResponse |
||||
|
from django.views.decorators.csrf import csrf_exempt |
||||
|
from django.core.serializers import serialize |
||||
|
|
||||
|
from player.models import Playlist, Link |
||||
|
from player.forms import PlaylistForm, LinkForm |
||||
|
|
||||
|
def new_playlist(request): |
||||
|
p = PlaylistForm(request.POST or None) |
||||
|
if p.is_valid(): |
||||
|
playlist = p.save() |
||||
|
return redirect(playlist.get_absolute_url()) |
||||
|
return render(request, 'form.html', { |
||||
|
'form':p, |
||||
|
'validate':'Créer', |
||||
|
'title':'Nouvelle playlist' |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
@csrf_exempt |
||||
|
def get_list(request, token): |
||||
|
p = get_object_or_404(Playlist, pk=Playlist.reverse_token(token)) |
||||
|
|
||||
|
s = serialize('json', p.link_set.all(), fields=('token',)) |
||||
|
return HttpResponse(s, content_type='application/json') |
||||
|
|
||||
|
|
||||
|
@csrf_exempt |
||||
|
def add_link(request, token): |
||||
|
p = get_object_or_404(Playlist, pk=Playlist.reverse_token(token)) |
||||
|
l = LinkForm(request.POST or None) |
||||
|
if l.is_valid(): |
||||
|
yt_token = l.get_token() |
||||
|
link = Link() |
||||
|
link.token = yt_token |
||||
|
link.playlist = p |
||||
|
link.save() |
||||
|
return HttpResponse('Ok') |
||||
|
return render(request, 'form_inline.html', { |
||||
|
'form':l, |
||||
|
'validate':'Ajouter' |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
def remove_link(request, pk): |
||||
|
l = get_object_or_404(Link, pk=pk) |
||||
|
l.delete() |
||||
|
return HttpResponse('Ok') |
||||
|
|
||||
|
|
||||
|
def playlist(request, token): |
||||
|
p = get_object_or_404(Playlist, pk=Playlist.reverse_token(token)) |
||||
|
add_link_form = LinkForm() |
||||
|
return render(request, 'player/playlist.html', { |
||||
|
'playlist':p, |
||||
|
'form':add_link_form |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
def all_playlist(request): |
||||
|
p = Playlist.objects.all().order_by('date') |
||||
|
return render(request, 'player/all_list.html', {'lists':p}) |
||||
|
|||||
@ -0,0 +1,27 @@ |
|||||
|
{% load bootstrap4 %} |
||||
|
<!DOCTYPE html> |
||||
|
<html lang="fr"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<title>LSY{% block title %}{% endblock %}</title> |
||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> |
||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> |
||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> |
||||
|
<script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script> |
||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> |
||||
|
</head> |
||||
|
<body> |
||||
|
{% include 'navbar.html' %} |
||||
|
<main role="main" class="container"> |
||||
|
<br/> |
||||
|
{% bootstrap_messages %} |
||||
|
|
||||
|
{% block content %}{% endblock %} |
||||
|
</main> |
||||
|
<hr/> |
||||
|
<footer class="footer container text-center text-muted"> |
||||
|
LSY by Klafyvel |
||||
|
</footer> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,14 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
{% load bootstrap4 %} |
||||
|
{% block title %} - {{title}}{%endblock%} |
||||
|
{% block content %} |
||||
|
<h3>{{title}}</h3> |
||||
|
<form action="" method="post" class="form"> |
||||
|
{% csrf_token %} |
||||
|
{% bootstrap_form form %} |
||||
|
<button class="btn btn-outline-primary"><i class="fa fa-star"></i> {{validate}}</button> |
||||
|
</form> |
||||
|
<br> |
||||
|
<br> |
||||
|
<br> |
||||
|
{% endblock %} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{% load bootstrap4 %} |
||||
|
<form action="" method="post" class="form" id="{{id}}"> |
||||
|
{% csrf_token %} |
||||
|
{% bootstrap_form form %} |
||||
|
<button class="btn btn-outline-primary"><i class="fa fa-star"></i> {{validate}}</button> |
||||
|
</form> |
||||
@ -0,0 +1,3 @@ |
|||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> |
||||
|
<a class="navbar-brand" href="/"><i class="fa fa-users"></i> LSY</a> |
||||
|
</nav> |
||||
Loading…
Reference in new issue