[radio] Un setup pour diffuser de la radio paresseusement
This commit is contained in:
parent
1be3bdb65c
commit
03f9be4008
7 changed files with 479 additions and 0 deletions
33
tv/radio/config.py
Normal file
33
tv/radio/config.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
import os, random, collections
|
||||||
|
|
||||||
|
def dir(path):
|
||||||
|
l=collections.deque(["%s%s" % (path,i) for i in os.listdir(path)])
|
||||||
|
l.rotate(random.randrange(0,len(l)))
|
||||||
|
return l
|
||||||
|
multicast={
|
||||||
|
'Radio': {
|
||||||
|
'Armitunes': ('armitunes','239.231.140.162','1234',['http://149.255.33.76:8004/','http://95.31.11.136:9010/','http://95.31.3.225:9010/','http://hanyo.dyndns-server.com:9078/']),
|
||||||
|
'Radio Classique': ('classique','239.231.140.163','1234',['http://broadcast.infomaniak.net:80/radioclassique-high.mp3']),
|
||||||
|
'France Inter': ('inter','239.231.140.164','1234',['http://mp3.live.tv-radio.com/franceinter/all/franceinterhautdebit.mp3']),
|
||||||
|
'France Info': ('info','239.231.140.165','1234',['http://mp3.live.tv-radio.com/franceinfo/all/franceinfo-32k.mp3']),
|
||||||
|
'Webradio Chibre': ('chibre','239.231.140.166','1234',['http://webradio.crans.org:8000/chibre.mp3']),
|
||||||
|
'Webradio Clubbing': ('clubbing','239.231.140.167','1234',['http://webradio.crans.org:8000/clubbing.mp3']),
|
||||||
|
'Webradio Rock': ('rock','239.231.140.168','1234',['http://webradio.crans.org:8000/rock.mp3']),
|
||||||
|
'I.ACTIVE DANCE': ('iactive','239.231.140.170', '1234', ['http://serveur.wanastream.com:48700/']),
|
||||||
|
'Skyrock': ('skyrock', '239.231.140.171', '1234', ['http://95.81.146.6/3665/sky_122353.mp3']),
|
||||||
|
'Rire et Chanson': ('rireetchanson', '239.231.140.172', '1234', ['http://95.81.146.2/rire_et_chansons/all/rir_124629.mp3']),
|
||||||
|
'Europe 1': ('europe1', '239.231.140.173', '1234', ['http://vipicecast.yacast.net/europe1.mp3']),
|
||||||
|
'Chérie FM': ('cherie_fm', '239.231.140.174', '1234', ['http://95.81.146.2/cherie_fm/all/che_124310.mp3']),
|
||||||
|
'France Culture': ('culture', '239.231.140.175', '1234', ['http://95.81.147.3/franceculture/all/franceculturehautdebit.mp3']),
|
||||||
|
'BFM': ('bfm', '239.231.140.176', '1234', ['http://vipicecast.yacast.net/bfm.mp3']),
|
||||||
|
'France Musique': ('musique', '239.231.140.177', '1234', ['http://95.81.147.3/francemusique/all/francemusiquehautdebit.mp3']),
|
||||||
|
'Fun Radio': ('funradio', '239.231.140.178', '1234', ['http://streaming.radio.funradio.fr/fun-1-44-128.mp3']),
|
||||||
|
'Nostalgie': ('nostalgie', '239.231.140.179', '1234', ['http://95.81.146.2/nostalgie/all/nos_113812.mp3']),
|
||||||
|
'le mouv\'': ('lemouv', '239.231.140.180', '1234', ['http://95.81.147.3/lemouv/all/lemouvhautdebit.mp3']),
|
||||||
|
'NRJ': ('nrj', '239.231.140.181', '1234', ['http://95.81.146.2/nrj/all/nrj_113225.mp3']),
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
37
tv/radio/dns.py
Normal file
37
tv/radio/dns.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
|
||||||
|
dns={}
|
||||||
|
def ip2name(ip):
|
||||||
|
if dns.get(ip, None) and time.time() - dns[ip][1] < 600:
|
||||||
|
return dns[ip][0]
|
||||||
|
try:
|
||||||
|
ret = timeout(socket.gethostbyaddr, args=[ip], default=(ip,), timeout_duration=1)[0]
|
||||||
|
except herror:
|
||||||
|
ret = ip
|
||||||
|
dns[ip]=(ret, time.time())
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def timeout(func, args=(), kwargs={}, timeout_duration=10, default=None):
|
||||||
|
"""This function will spawn a thread and run the given function
|
||||||
|
using the args, kwargs and return the given default value if the
|
||||||
|
timeout_duration is exceeded.
|
||||||
|
"""
|
||||||
|
import threading
|
||||||
|
class InterruptableThread(threading.Thread):
|
||||||
|
def __init__(self):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.result = default
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.result = func(*args, **kwargs)
|
||||||
|
except:
|
||||||
|
self.result = default
|
||||||
|
it = InterruptableThread()
|
||||||
|
it.start()
|
||||||
|
it.join(timeout_duration)
|
||||||
|
if it.isAlive():
|
||||||
|
|
||||||
|
return it.result
|
||||||
|
else:
|
||||||
|
return it.result
|
36
tv/radio/igmp.init
Executable file
36
tv/radio/igmp.init
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/bash
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: igmp_tracker
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: igmp subscribe tracker
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
DAEMON="/usr/bin/python"
|
||||||
|
ARGS="/usr/scripts/tv/radio/igmp.py"
|
||||||
|
PIDFILE="/var/run/igmp.pid"
|
||||||
|
USER="root"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo "Starting server"
|
||||||
|
/sbin/start-stop-daemon --start --pidfile $PIDFILE \
|
||||||
|
--user $USER --group $USER \
|
||||||
|
-b --make-pidfile \
|
||||||
|
--chuid $USER \
|
||||||
|
--exec $DAEMON $ARGS
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo "Stopping server"
|
||||||
|
/sbin/start-stop-daemon --stop --pidfile $PIDFILE --verbose
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
255
tv/radio/igmp.py
Executable file
255
tv/radio/igmp.py
Executable file
|
@ -0,0 +1,255 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import socket
|
||||||
|
from struct import *
|
||||||
|
import datetime
|
||||||
|
import pcapy
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from dns import ip2name
|
||||||
|
|
||||||
|
import config
|
||||||
|
|
||||||
|
group_to_vlm={}
|
||||||
|
|
||||||
|
for g in config.multicast.values():
|
||||||
|
for tuple in g.values():
|
||||||
|
group_to_vlm[tuple[1]]=group_to_vlm.get(tuple[1], []) + [tuple[0]]
|
||||||
|
|
||||||
|
def run_vlc(cmd):
|
||||||
|
try:
|
||||||
|
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.connect(('127.0.0.1', 4212))
|
||||||
|
ret=s.recv(65536)
|
||||||
|
s.send('admin\n')
|
||||||
|
#~ print cmd
|
||||||
|
s.send(cmd+'\n')
|
||||||
|
s.settimeout(0.3)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
ret+=s.recv(65536)
|
||||||
|
except socket.timeout:
|
||||||
|
pass
|
||||||
|
s.close()
|
||||||
|
return ret
|
||||||
|
except socket.error:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def stop(group):
|
||||||
|
if group_to_vlm.get(group, None):
|
||||||
|
cmd='\n'.join('control %s stop' % name for name in group_to_vlm[group])
|
||||||
|
run_vlc(cmd)
|
||||||
|
|
||||||
|
def play(group):
|
||||||
|
if group_to_vlm.get(group, None):
|
||||||
|
cmd='\n'.join('control %s play' % name for name in group_to_vlm[group])
|
||||||
|
run_vlc(cmd)
|
||||||
|
|
||||||
|
def playing(group):
|
||||||
|
ret=True
|
||||||
|
if group_to_vlm.get(group, None):
|
||||||
|
for name in group_to_vlm[group]:
|
||||||
|
ret=ret and 'playing' in run_vlc('show %s' % name)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class IgmpTable(object):
|
||||||
|
|
||||||
|
def __init__(self, dev, ignore_src=[], debug=0, resolve_dns=False, logpath=None):
|
||||||
|
self.CLEAN_FREQ=10
|
||||||
|
self.MC_REPORT=300
|
||||||
|
self.CLEAN_LAST=0
|
||||||
|
self.STREAM_LAST=time.time()
|
||||||
|
self.table={}
|
||||||
|
self.debug=debug
|
||||||
|
self.counter={}
|
||||||
|
self.dev=dev
|
||||||
|
self.ignore_src=ignore_src
|
||||||
|
self.resolve_dns=resolve_dns
|
||||||
|
self.logpath=logpath
|
||||||
|
|
||||||
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IGMP)
|
||||||
|
self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
cap = pcapy.open_live(self.dev , 65536 , 1 , self.CLEAN_FREQ * 1000)
|
||||||
|
cap.setfilter('igmp')
|
||||||
|
|
||||||
|
#start sniffing packets
|
||||||
|
try:
|
||||||
|
while(True) :
|
||||||
|
try:
|
||||||
|
(header, packet) = cap.next()
|
||||||
|
tuple=self.parse_packet(packet)
|
||||||
|
if tuple and tuple[0][0] not in self.ignore_src:
|
||||||
|
# type: Membership Query (17), Membership Report (IGMPv1: 18, IGMPv2: 22, IGMPv3: 34), Leave Group (23)
|
||||||
|
((src, dst), type, group) = tuple
|
||||||
|
if type in [18, 22, 34]:
|
||||||
|
#print "%s report %s" % (src, group)
|
||||||
|
self.report(group, src)
|
||||||
|
elif type == 23:
|
||||||
|
#print "%s leaving %s" % (src, group)
|
||||||
|
self.leave(group, src)
|
||||||
|
#else:
|
||||||
|
# print type
|
||||||
|
self.clean()
|
||||||
|
self.clean_stream()
|
||||||
|
except socket.timeout:
|
||||||
|
pass
|
||||||
|
except (KeyboardInterrupt,):
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
def query(self, group):
|
||||||
|
igmph_length = 8
|
||||||
|
igmp_type = 17
|
||||||
|
max_resp_time = 10 #self.CLEAN_FREQ * 5
|
||||||
|
igmp_group = ''.join(str(chr(int(i))) for i in group.split('.'))
|
||||||
|
igmp = pack('!BBBB' , igmp_type, max_resp_time, 0, 0) + igmp_group
|
||||||
|
|
||||||
|
def carry_around_add(a, b):
|
||||||
|
c = a + b
|
||||||
|
return (c & 0xffff) + (c >> 16)
|
||||||
|
|
||||||
|
def checksum(msg):
|
||||||
|
s = 0
|
||||||
|
for i in range(0, len(msg), 2):
|
||||||
|
w = ord(msg[i]) + (ord(msg[i+1]) << 8)
|
||||||
|
s = carry_around_add(s, w)
|
||||||
|
return ~s & 0xffff
|
||||||
|
|
||||||
|
igmp=igmp[0:2] + pack('H', checksum(igmp)) + igmp[4:]
|
||||||
|
self.sock.sendto(igmp, (group, 0))
|
||||||
|
|
||||||
|
def leave(self, group, src):
|
||||||
|
tp=time.time()
|
||||||
|
if self.table.get(group, None) and self.table[group].get(src, None) and tp - self.table[group][src] < self.MC_REPORT:
|
||||||
|
if len([ip for ip in self.table[group].keys() if tp - self.table[group][ip] < self.MC_REPORT])<=1:
|
||||||
|
self.CLEAN_LAST=time.time() - 5
|
||||||
|
self.table[group][src]=0
|
||||||
|
#self.query(group) #Inutile, mcproxy le fait
|
||||||
|
if self.debug>0:
|
||||||
|
self.print_table()
|
||||||
|
self.log()
|
||||||
|
else:
|
||||||
|
if not self.table.get(group, None):
|
||||||
|
self.table[group]={}
|
||||||
|
self.counter[group]=0
|
||||||
|
self.query(group)
|
||||||
|
|
||||||
|
#function to parse a packet
|
||||||
|
def parse_packet(self, packet) :
|
||||||
|
|
||||||
|
#parse ethernet header
|
||||||
|
eth_length = 14
|
||||||
|
|
||||||
|
eth_header = packet[:eth_length]
|
||||||
|
eth = unpack('!6s6sH' , eth_header)
|
||||||
|
eth_protocol = socket.ntohs(eth[2])
|
||||||
|
|
||||||
|
#Parse IP packets, IP Protocol number = 8
|
||||||
|
if eth_protocol == 8 :
|
||||||
|
#Parse IP header
|
||||||
|
#take first 20 characters for the ip header
|
||||||
|
ip_header = packet[eth_length:20+eth_length]
|
||||||
|
|
||||||
|
#now unpack them :)
|
||||||
|
iph = unpack('!BBHHHBBH4s4s' , ip_header)
|
||||||
|
|
||||||
|
version_ihl = iph[0]
|
||||||
|
version = version_ihl >> 4
|
||||||
|
ihl = version_ihl & 0xF
|
||||||
|
|
||||||
|
iph_length = ihl * 4
|
||||||
|
|
||||||
|
ttl = iph[5]
|
||||||
|
protocol = iph[6]
|
||||||
|
s_addr = socket.inet_ntoa(iph[8]);
|
||||||
|
d_addr = socket.inet_ntoa(iph[9]);
|
||||||
|
|
||||||
|
|
||||||
|
#IGMP Packets
|
||||||
|
if protocol == 2 :
|
||||||
|
u = iph_length + eth_length
|
||||||
|
igmph_length = 8
|
||||||
|
igmp_header = packet[u:u+4]
|
||||||
|
igmp_group = socket.inet_ntoa(packet[u+4:u+8])
|
||||||
|
igmph = unpack('!BBH' , igmp_header)
|
||||||
|
|
||||||
|
igmp_type = igmph[0]
|
||||||
|
max_resp_time = igmph[1]
|
||||||
|
checksum = igmph[2]
|
||||||
|
|
||||||
|
return ((s_addr, d_addr), igmp_type, igmp_group)
|
||||||
|
|
||||||
|
#h_size = eth_length + iph_length + igmph_length
|
||||||
|
#data_size = len(packet) - h_size
|
||||||
|
##get data from the packet
|
||||||
|
#data = packet[h_size:]
|
||||||
|
|
||||||
|
def report(self, group, ip):
|
||||||
|
try:
|
||||||
|
if not self.table[group].get(ip, None):
|
||||||
|
self.counter[group]+=1
|
||||||
|
play(group)
|
||||||
|
self.table[group][ip]=time.time()
|
||||||
|
except KeyError:
|
||||||
|
self.table[group]={ip:time.time()}
|
||||||
|
self.counter[group]=1
|
||||||
|
play(group)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if time.time() - self.CLEAN_LAST < self.CLEAN_FREQ:
|
||||||
|
return
|
||||||
|
for (group, ips) in self.table.items():
|
||||||
|
for (ip, last) in ips.items():
|
||||||
|
if time.time() - last > self.MC_REPORT:
|
||||||
|
del(self.table[group][ip])
|
||||||
|
self.counter[group]-=1
|
||||||
|
if not self.table[group]:
|
||||||
|
del(self.table[group])
|
||||||
|
del(self.counter[group])
|
||||||
|
stop(group)
|
||||||
|
elif not playing(group):
|
||||||
|
play(group)
|
||||||
|
self.CLEAN_LAST=time.time()
|
||||||
|
if self.debug > 0:
|
||||||
|
self.print_table()
|
||||||
|
self.log()
|
||||||
|
|
||||||
|
def clean_stream(self):
|
||||||
|
if time.time() - self.STREAM_LAST < self.MC_REPORT:
|
||||||
|
return
|
||||||
|
for group in group_to_vlm.keys():
|
||||||
|
if not self.counter.get(group, None):
|
||||||
|
stop(group)
|
||||||
|
self.STREAM_LAST=time.time()
|
||||||
|
|
||||||
|
|
||||||
|
def print_table(self):
|
||||||
|
print self.summarise()
|
||||||
|
|
||||||
|
def log(self):
|
||||||
|
if self.logpath:
|
||||||
|
f=open(self.logpath, 'w')
|
||||||
|
f.write(self.summarise())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def summarise(self):
|
||||||
|
l=self.table.items()
|
||||||
|
l.sort()
|
||||||
|
tp=time.time()
|
||||||
|
def resolve(ip):
|
||||||
|
if self.resolve_dns:
|
||||||
|
return ip2name(ip)
|
||||||
|
else:
|
||||||
|
return ip
|
||||||
|
ret=""
|
||||||
|
for (group, ips) in l:
|
||||||
|
ret+= "%s: %s\n" % (resolve(group), ', '.join(["%s (%ss)" % (resolve(k[0]), int(tp - k[1])) for k in ips.items()]) if len(ips) < 10 else "%s clients" % len(ips))
|
||||||
|
ret+= "-"*80 + "\n"
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
IgmpTable('eth0', ignore_src=['138.231.136.88', '0.0.0.0'], logpath='/var/log/igmp.log').run()
|
80
tv/radio/radio.init
Executable file
80
tv/radio/radio.init
Executable file
|
@ -0,0 +1,80 @@
|
||||||
|
#!/bin/bash
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: radio
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: network radio
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
DIR="/usr/scripts/tv/radio/"
|
||||||
|
PIDFILE="/tmp/multicast.pid"
|
||||||
|
TMP="/tmp/"
|
||||||
|
sap_gen(){
|
||||||
|
PREV="`md5sum /etc/sap.cfg`"
|
||||||
|
${DIR}sap.py > /etc/sap.cfg
|
||||||
|
NEXT="`md5sum /etc/sap.cfg`"
|
||||||
|
if [ "$PREV" != "$NEXT" ]; then
|
||||||
|
/etc/init.d/minisapserver force-reload >/dev/null
|
||||||
|
fi;
|
||||||
|
}
|
||||||
|
vlc_start() {
|
||||||
|
if [ -f $PIDFILE ]; then
|
||||||
|
pid=`cat $PIDFILE`
|
||||||
|
if [ `ps -p $pid | wc -l` -eq 2 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
${DIR}vlm.py > ${TMP}vlm.vlm
|
||||||
|
/usr/bin/sudo -u www-data cvlc --extraintf telnet --ttl 12 --vlm-conf ${TMP}vlm.vlm --pidfile ${PIDFILE} --daemon >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vlc_stop(){
|
||||||
|
if [ -f $PIDFILE ]; then
|
||||||
|
pid=`cat $PIDFILE`
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
vlc_status(){
|
||||||
|
if [ -f $PIDFILE ]; then
|
||||||
|
pid=`cat $PIDFILE`
|
||||||
|
if [ `ps -p $pid | wc -l` -eq 2 ]; then
|
||||||
|
echo "VLC is running."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "VLC is NOT running."
|
||||||
|
}
|
||||||
|
|
||||||
|
usage(){
|
||||||
|
echo "Usage: $0 {start|stop|restart}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
start)
|
||||||
|
sap_gen;
|
||||||
|
vlc_start;
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
vlc_stop;
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
sap_gen;
|
||||||
|
for i in `seq 10`; do
|
||||||
|
vlc_stop;
|
||||||
|
sleep 1;
|
||||||
|
if [ ! -f $PIDFILE ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo -n "."
|
||||||
|
done
|
||||||
|
vlc_start;
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage;
|
||||||
|
;;
|
||||||
|
esac
|
21
tv/radio/sap.py
Executable file
21
tv/radio/sap.py
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
from config import *
|
||||||
|
import socket
|
||||||
|
|
||||||
|
print("""[global]
|
||||||
|
sap_delay=9
|
||||||
|
interface=eth0
|
||||||
|
#ttl=1
|
||||||
|
""")
|
||||||
|
|
||||||
|
for group in multicast.keys():
|
||||||
|
for name in multicast[group].keys():
|
||||||
|
(chan,dst,port,source)=multicast[group][name]
|
||||||
|
print("""[program]
|
||||||
|
name=%s
|
||||||
|
address=%s
|
||||||
|
port=%s
|
||||||
|
playlist_group=%s
|
||||||
|
user=crans
|
||||||
|
machine=%s
|
||||||
|
""" % (name,dst,port,group, socket.gethostname()))
|
17
tv/radio/vlm.py
Executable file
17
tv/radio/vlm.py
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
i=0
|
||||||
|
for group in multicast.keys():
|
||||||
|
for (name,dst,port,sources) in multicast[group].values():
|
||||||
|
#name="channel%s" % i
|
||||||
|
print('new %s broadcast enabled loop' % name)
|
||||||
|
for source in sources:
|
||||||
|
print('setup %s input "%s"' % (name,source))
|
||||||
|
print('setup %s output #udp{mux=ts,dst=%s:%s}' % (name,dst,port))
|
||||||
|
print('setup %s option network-caching=50' % name)
|
||||||
|
print('setup %s option sout-all' % name)
|
||||||
|
print('setup %s option sout-keep' % name)
|
||||||
|
#print('control %s play' % name)
|
||||||
|
i+=1
|
Loading…
Add table
Add a link
Reference in a new issue