Initial working version.

feature/data-first
Daniel Perelman преди 4 години
ревизия 5c6ca82c13

61
.gitignore vendored

@ -0,0 +1,61 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# sqlite database
*.sqlite3

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CameraConfig(AppConfig):
name = 'camera'

@ -0,0 +1,42 @@
from channels.generic.websocket import AsyncWebsocketConsumer
class VideoConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.kind = self.scope['url_route']['kwargs']['kind']
self.listen_group_name = '%s_%s' % (self.kind, self.room_name)
other_kind = 'client' if self.kind == 'host' else 'host'
self.send_group_name = '%s_%s' % (other_kind, self.room_name)
# Join room group
await self.channel_layer.group_add(
self.listen_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.listen_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
await self.channel_layer.group_send(
self.send_group_name,
{
'type': 'chat_message',
'message': text_data
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=message)

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

@ -0,0 +1,8 @@
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'camera/ws/(?P<kind>host|client)/(?P<room_name>\w+)/$',
consumers.VideoConsumer),
]

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Simple WebRTC</title>
<style>
body.justVideo {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<a id="qrcodelink" style="display:none;"><img id="qrcode" /></a>
<span id="status"></span>
<div id="videos" style="display:none;">
<video id="remoteView" width="100%" autoplay muted></video>
<video id="selfView" width="200" height="150" autoplay></video>
</div>
<script >
const create = (container, type) => container.appendChild(document.createElement(type));
const body = document.querySelector("body");
const out = document.getElementById("status");
const qrcode = document.getElementById("qrcode");
const qrcodelink = document.getElementById("qrcodelink");
const remoteView = document.getElementById("remoteView");
remoteView.style.display = 'none';
out.innerText += "Loading...\n";
function getRoomName() {
JSON.parse(document.getElementById('room-name').textContent);
}
let roomName = window.location.hash;
let isHost = roomName === undefined || !roomName;
if (isHost) {
// From https://stackoverflow.com/a/1349426
function makeid(length) {
var result = '';
var characters = 'abcdefghijklmnopqrstuvwxyz';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
roomName = makeid(8);
qrcodelink.href = window.location.href.split('#')[0] + '#' + roomName;
qrcode.src = window.location.href.split('#')[0] + roomName + '/qr';
qrcodelink.style.display = '';
} else {
roomName = roomName.substring(1);
qrcodelink.style.display = 'none';
}
out.innerText += "Room: " + roomName + "\n";
var webSocket = undefined;
function sendJson(data) {
const toSend = JSON.stringify(data);
out.innerText += "Sending message...\n";
create(out, 'pre').innerText = toSend.split('\\r\\n').join('\r\n');
create(out, 'br');
webSocket.send(toSend);
}
var pc = undefined;
function createRTCPeerConnection() {
const pc = new RTCPeerConnection();
out.innerText += "Created RTCPeerConnection.\n";
pc.onicecandidate = ({candidate}) => sendJson({candidate});
// let the "negotiationneeded" event trigger offer generation
pc.onnegotiationneeded = async function () {
out.innerText += "In pc.onnegotiationneeded...\n";
await pc.setLocalDescription(await pc.createOffer());
sendJson({
description: pc.localDescription
});
}
pc.ontrack = ({streams: [stream]}) => {
out.innerText += "In pc.ontrack...\n";
remoteView.srcObject = stream;
remoteView.style.display = '';
remoteView.play();
out.innerText += "Set srcObject\n";
out.style.display = 'none';
videos.style.display = '';
body.classList.add('justVideo');
};
return pc;
}
async function receiveMessage(e) {
if (pc === undefined) pc = createRTCPeerConnection();
qrcode.style.display = 'none';
out.innerText += "In webSocket.onmessage...\n";
create(out, 'pre').innerText = e.data.split('\\r\\n').join('\r\n');
create(out, 'br');
const data = JSON.parse(e.data);
if (data.description) {
await pc.setRemoteDescription(data.description);
if (data.description.type == "offer") {
out.innerText += "Got an offer...\n";
await pc.setLocalDescription(await pc.createAnswer());
sendJson({
description: pc.localDescription
});
}
} else if (data.candidate) {
out.innerText += "Adding ice candidate...\n";
await pc.addIceCandidate(data.candidate);
}
};
function createWebSocket() {
const webSocket = new WebSocket(
'ws' + (window.location.protocol == 'https:' ? 's' : '') + '://'
+ window.location.host
+ '/camera/ws/' + (isHost ? 'host' : 'client') + '/'
+ roomName
+ '/'
);
out.innerText += "Created WebSocket.\n";
webSocket.onclose = function(e) {
console.error('Web socket closed unexpectedly');
};
webSocket.onmessage = receiveMessage;
return webSocket;
}
webSocket = createWebSocket();
// get a local stream, show it in a self-view and add it to be sent
async function startStreaming() {
pc = createRTCPeerConnection();
const videoConstraints = { advanced: [{facingMode: "environment"}] };
out.innerText += "Created videoConstraints.\n";
const stream = await navigator.mediaDevices.getUserMedia({ "audio": false, "video": videoConstraints });
out.innerText += "Created stream.\n";
selfView.srcObject = stream;
for (const track of stream.getTracks()) {
out.innerText += "Added track.\n";
pc.addTrack(track, stream);
}
}
if (!isHost) {
startStreaming()
.then(() => {
out.innerText += "startStreaming() finished.\n";
})
.catch(e => {
out.innerText += "startStreaming() errored: " + e.message + "\n";
})
;
}
out.innerText += "Finished <script> block.\n";
</script>
</body>
</html>

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,8 @@
from django.urls import path
from . import views
urlpatterns = [
path('camera/', views.camera, name='camera'),
path('camera/<str:room_name>/qr', views.camera_qr, name='camera_qr'),
]

@ -0,0 +1,19 @@
from io import BytesIO
import qrcode
from django.http import HttpResponse
from django.shortcuts import render
from django.urls import reverse
def camera(request):
return render(request, 'index.html')
def camera_qr(request, room_name):
camera_url = reverse('camera')
camera_url = request.build_absolute_uri(camera_url) + '#' + room_name
img = qrcode.make(camera_url)
output = BytesIO()
img.save(output, "PNG")
return HttpResponse(output.getvalue(), content_type='image/png')

@ -0,0 +1,12 @@
"""
ASGI entrypoint. Configures Django and then runs the application
defined in the ASGI_APPLICATION setting.
"""
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "camera_site.settings")
django.setup()
application = get_default_application()

@ -0,0 +1,9 @@
from channels.routing import ProtocolTypeRouter, URLRouter
import camera.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': URLRouter(
camera.routing.websocket_urlpatterns
),
})

@ -0,0 +1,94 @@
"""
Django settings for camera_site project.
Generated by 'django-admin startproject' using Django 3.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'xh9dx24&9r8yx!@@qyjn@b7z%b-_mc&@itrv&52z#8@=08sdlr'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'channels',
'camera',
'django.contrib.contenttypes',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'camera_site.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
],
},
},
]
WSGI_APPLICATION = 'camera_site.wsgi.application'
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
ASGI_APPLICATION = 'camera_site.routing.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

@ -0,0 +1,21 @@
"""camera_site URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls import include
from django.urls import path
urlpatterns = [
path('', include('camera.urls')),
]

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'camera_site.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

@ -0,0 +1,5 @@
#!/bin/sh
export DJANGO_SETTINGS_MODULE=camera_site.local_settings
daphne -u site.sock camera_site.asgi:application

@ -0,0 +1 @@
chromium --incognito --app=https://localhost/viewer/lobby/
Зареждане…
Отказ
Запис