文件:index.html
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- jquery -->
<script src="jquery-3.5.1.min.js"></script>
<!-- own javascript -->
<script src="telephone_calls.js"></script>
</head>
<body>
<audio style="width:100%;margin-top:5mm;background:rgb(241,243,244)" id="audio" autoplay="true" controls="true"></audio>
</body>
</html>
文件phone_calls.js
// peer connection
var pc = null;
function createPeerConnection() {
var config = {
sdpSemantics: 'unified-plan',
iceServers: [{urls: ['stun:stun.l.google.com:19302']}]
};
pc = new RTCPeerConnection(config);
// connect audio
pc.addEventListener('track', function(evt) {
if (evt.track.kind == 'audio'){
document.getElementById('audio').srcObject = evt.streams[0];
};
});
return pc;
}
function negotiate() {
return pc.createOffer().then(function(offer) {
return pc.setLocalDescription(offer);
}).then(function() {
// wait for ICE gathering to complete
return new Promise(function(resolve) {
console.log(pc.iceGatheringState);
if (pc.iceGatheringState === 'complete') {
resolve();
} else {
function checkState() {
console.log(pc.iceGatheringState);
if (pc.iceGatheringState === 'complete') {
pc.removeEventListener('icegatheringstatechange', checkState);
resolve();
}
}
pc.addEventListener('icegatheringstatechange', checkState);
}
});
}).then(function() {
var offer = pc.localDescription;
return fetch('/offer', {
body: JSON.stringify({
sdp: offer.sdp,
type: offer.type
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
}).then(function(response) {
return response.json();
}).then(function(answer) {
return pc.setRemoteDescription(answer);
}).catch(function(e) {
alert(e);
console.log(e);
});
}
function start() {
pc = createPeerConnection();
dc = pc.createDataChannel('radio_metadata', {"ordered": true});
dc.onclose = function() {
};
dc.onopen = function() {
};
dc.onmessage = function(evt) {
console.log(evt.data);
};
//negotiate();
constraints = {audio:true,video:false};
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
stream.getTracks().forEach(function(track) {
pc.addTrack(track, stream);
});
return negotiate();
}, function(err) {
alert('Could not acquire media: ' + err);
});
}
$(document).ready(function(){
start();
})
文件:server.py
from aiohttp import web
from aiortc.mediastreams import MediaStreamTrack
from aiortc import RTCPeerConnection, RTCSessionDescription
from aiortc.contrib.media import MediaPlayer
from pydub import AudioSegment
import av
import pyaudio
import asyncio
import json
import os
from multiprocessing import Process, freeze_support
from queue import Queue
import sys
import threading
from time import sleep
import fractions
import time
class RadioServer(Process):
def __init__(self,q):
super().__init__()
self.q = q
self.ROOT = os.path.dirname(__file__)
self.pcs = []
self.channels = []
self.stream_offers = []
def run(self):
self.app = web.Application()
self.app.on_shutdown.append(self.on_shutdown)
self.app.router.add_get("/", self.index)
self.app.router.add_get("/telephone_calls.js", self.javascript)
self.app.router.add_get("/jquery-3.5.1.min.js", self.jquery)
self.app.router.add_post("/offer", self.offer)
threading.Thread(target=self.fill_the_queues).start()
web.run_app(self.app, access_log=None, host="192.168.1.20", port="8080", ssl_context=None)
def fill_the_queues(self):
while(True):
frame = self.q.get()
for stream_offer in self.stream_offers:
stream_offer.q.put(frame)
async def index(self,request):
content = open(os.path.join(self.ROOT, "index.html"), encoding="utf8").read()
return web.Response(content_type="text/html", text=content)
async def javascript(self,request):
content = open(os.path.join(self.ROOT, "telephone_calls.js"), encoding="utf8").read()
return web.Response(content_type="application/javascript", text=content)
async def jquery(self,request):
content = open(os.path.join(self.ROOT, "jquery-3.5.1.min.js"), encoding="utf8").read()
return web.Response(content_type="application/javascript", text=content)
async def offer(self,request):
params = await request.json()
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
self.pcs.append(pc)
# prepare epalxeis media
self.stream_offers.append(CustomRadioStream())
pc.addTrack(self.stream_offers[-1])
#player = MediaPlayer(os.path.join(self.ROOT, "ΑΓΙΑ ΚΥΡΙΑΚΗ.mp3"))
#pc.addTrack(player.audio)
@pc.on("datachannel")
def on_datachannel(channel):
self.channels.append(channel)
self.send_channel_message(str(len(self.pcs)))
@pc.on("iceconnectionstatechange")
async def on_iceconnectionstatechange():
if pc.iceConnectionState == "failed":
self.pcs.remove(pc)
print("Current peer connections:"+str(len(self.pcs)))
# handle offer
await pc.setRemoteDescription(offer)
# send answer
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
return web.Response(content_type="application/json",text=json.dumps({"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}))
async def on_shutdown(self,app):
# close peer connections
if self.pcs:
coros = [pc.close() for pc in self.pcs]
await asyncio.gather(*coros)
self.pcs = []
self.channels = []
self.stream_offers = []
def send_channel_message(self,message):
for channel in self.channels:
channel.send(message)
class CustomRadioStream(MediaStreamTrack):
kind = "audio"
def __init__(self):
super().__init__() # don't forget this!
self.q = Queue()
self._start = None
async def recv(self):
frame = self.q.get()
frame_time = frame.time
if self._start is None:
self._start = time.time() - frame_time
else:
wait = self._start + frame_time - time.time()
await asyncio.sleep(wait)
return frame
class RadioOutputStream:
def __init__(self,q):
self.sample_rate = 44800
self.AUDIO_PTIME = 0.744
self.samples = int(self.AUDIO_PTIME * self.sample_rate)
self.packet_time = 20
self.FORMAT = pyaudio.paInt16
self.CHANNELS = 2
self.RATE = self.sample_rate
self.CHUNK = int(44100*0.744)
self.file_segment = AudioSegment.from_file(r"ΑΓΙΑ ΚΥΡΙΑΚΗ.mp3").set_frame_rate(self.sample_rate)
self.duration_milliseconds = len(self.file_segment)
self.chunk_number = 0
self.silence = AudioSegment.silent(duration=self.packet_time)
self.q = q
self.codec = av.CodecContext.create('pcm_s16le', 'r')
self.codec.sample_rate = 44800
self.codec.channels = 2
self.audio_samples = 0
def run_stream(self):
while(True):
time_start = time.time()
if((self.chunk_number+1)*(self.packet_time)<=self.duration_milliseconds):
final_slice = self.file_segment[self.chunk_number*self.packet_time:(self.chunk_number+1)*self.packet_time]
#final_slice = final_slice + 100
self.chunk_number += 1
packet = av.Packet(final_slice.raw_data)
frame = self.codec.decode(packet)[0]
frame.pts = self.audio_samples
frame.time_base = fractions.Fraction(1, self.codec.sample_rate)
self.audio_samples += frame.samples
self.q.put(frame)
else:
if(self.chunk_number*self.packet_time<self.duration_milliseconds):
final_slice = self.file_segment[self.chunk_number*self.packet_time:]
final_slice = final_slice + self.silence
#final_slice = final_slice + 100
self.chunk_number += 1
packet = av.Packet(final_slice.raw_data)
frame = self.codec.decode(packet)[0]
frame.pts = self.audio_samples
frame.time_base = fractions.Fraction(1, self.codec.sample_rate)
self.audio_samples += frame.samples
self.q.put(frame)
else:
#start song from begin
self.chunk_number=0
#time_end = time.time()
#sleep_time = time_end-time_start
#if sleep_time>0.020:
# sleep(sleep_time-0.020)
sleep(0.01)
if __name__ == "__main__":
freeze_support()
q = Queue()
radio_stream = RadioOutputStream(q)
threading.Thread(target=radio_stream.run_stream).start()
custom_server_child_process = RadioServer(q)
custom_server_child_process.run()
我想改变这个:
constraints = {audio:true,video:false};
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
stream.getTracks().forEach(function(track) {
pc.addTrack(track, stream);
});
return negotiate();
}, function(err) {
alert('Could not acquire media: ' + err);
});
对此:
negotiate();
但如果我这样做,服务器端会出现以下错误:
C:\Users\Χρήστος\Desktop\radio>python server.py
======== Running on http://192.168.1.20:8080 ========
(Press CTRL+C to quit)
Error handling request
Traceback (most recent call last):
File "C:\Python\lib\site-packages\aiohttp\web_protocol.py", line 422, in _handle_request
resp = await self._request_handler(request)
File "C:\Python\lib\site-packages\aiohttp\web_app.py", line 499, in _handle
resp = await handler(request)
File "C:\Users\Χρήστος\Desktop\radio\server.py", line 90, in offer
await pc.setLocalDescription(answer)
File "C:\Python\lib\site-packages\aiortc\rtcpeerconnection.py", line 768, in setLocalDescription
t._currentDirection = and_direction(t.direction, t._offerDirection)
File "C:\Python\lib\site-packages\aiortc\rtcpeerconnection.py", line 247, in and_direction
return sdp.DIRECTIONS[sdp.DIRECTIONS.index(a) & sdp.DIRECTIONS.index(b)]
ValueError: None is not in list
谁能帮我创建一个空的报价?