Add WebRTC Internals Exporter extension with initial files and functionality
This commit is contained in:
@@ -4,6 +4,10 @@ import json
|
||||
import time
|
||||
import socket
|
||||
import logging
|
||||
import os
|
||||
from functools import partial
|
||||
from http.server import HTTPServer
|
||||
from utils.PostHandler import Handler
|
||||
from utils.ColoredFormatter import ColoredFormatter
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from selenium import webdriver
|
||||
@@ -26,7 +30,7 @@ def setupLogger():
|
||||
logger.addHandler(logger_handler)
|
||||
|
||||
def interrupt_handler(signum, driver: webdriver.Chrome):
|
||||
print(f'Handling signal {signum} ({signal.Signals(signum).name}).')
|
||||
logger.log(logging.INFO, f'Handling signal {signum} ({signal.Signals(signum).name}).')
|
||||
|
||||
schedule.clear()
|
||||
driver.quit()
|
||||
@@ -35,29 +39,33 @@ def interrupt_handler(signum, driver: webdriver.Chrome):
|
||||
def setupChromeDriver():
|
||||
logger.log(logging.INFO, 'Setting up Chrome driver.')
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument("--headless")
|
||||
#chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--mute-audio")
|
||||
chrome_options.add_argument("--window-size=1280,720")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
chrome_options.add_argument("--no-default-browser-check")
|
||||
chrome_options.add_argument("--disable-features=WebRtcHideLocalIpsWithMdns")
|
||||
chrome_options.add_argument(f"--load-extension={os.path.abspath(os.path.join(os.path.dirname(__file__), 'webrtc-internals-exporter'))}")
|
||||
chrome_options.add_experimental_option('prefs', {'intl.accept_languages': 'en,en_US'})
|
||||
#chrome_options.add_extension('./qryn-webrtc-exporter.crx')
|
||||
|
||||
|
||||
driver = webdriver.Chrome(options=chrome_options)
|
||||
#driver = webdriver.Remote(command_executor='http://localhost:4444', options=chrome_options)
|
||||
logger.log(logging.INFO, 'Chrome driver setup complete.')
|
||||
|
||||
return driver
|
||||
|
||||
def saveStats(stats: dict):
|
||||
def saveStats(stats: list):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
logger.log(logging.DEBUG, f'Saving stats: {json.dumps(stats, indent=4)}')
|
||||
sock.sendto(json.dumps(stats).encode(), ('localhost', 8094))
|
||||
sock.close()
|
||||
print(f'Sent stats: {stats}')
|
||||
logger.log(logging.DEBUG, 'Sent stats to socket.')
|
||||
except socket.error as e:
|
||||
print(f'Got socket error: {e}')
|
||||
logger.error(f'Got socket error: {e}')
|
||||
|
||||
def downloadStats(driver: webdriver.Chrome):
|
||||
def downloadStats(driver: webdriver.Chrome, peersDict: dict):
|
||||
html = driver.find_element(By.CLASS_NAME ,'vjs-stats-list').get_attribute('innerHTML')
|
||||
if html is not None:
|
||||
htmlBS = bs(html, 'html.parser')
|
||||
@@ -66,32 +74,91 @@ def downloadStats(driver: webdriver.Chrome):
|
||||
|
||||
stats = htmlBS.find_all('div', attrs={'style': 'display: block;'})
|
||||
|
||||
statsDict = {
|
||||
playerStats = {
|
||||
stat.div.text: stat.span.text.replace('\u21d3', 'down').replace('down/', 'down /').replace('\u21d1 ', 'up').replace('\u21d1', 'up').replace('\u00b7', '-').strip()
|
||||
for stat in stats
|
||||
}
|
||||
|
||||
for stat in statsDict:
|
||||
keys = list(playerStats.keys())
|
||||
for stat in keys:
|
||||
if 'Viewport / Frames' == stat:
|
||||
viewport, frames = playerStats[stat].split(' / ')
|
||||
width, height = viewport.split('x')
|
||||
height, devicePixelRatio = height.split('*')
|
||||
dropped, total = frames.split(' of ')[0].split()[0], frames.split(' of ')[1].split()[0]
|
||||
playerStats[stat] = {'Width': int(width), 'Height': int(height), 'Pixel ratio': float(devicePixelRatio), 'Frames': {'Dropped': int(dropped), 'Total': int(total)}}
|
||||
|
||||
if 'Codecs' == stat:
|
||||
video, audio = playerStats[stat].split(' / ')
|
||||
playerStats[stat] = {'Video': video, 'Audio': audio}
|
||||
|
||||
if 'Volume' == stat:
|
||||
if ' (' in playerStats[stat]:
|
||||
volume, muted = playerStats[stat].split(' (')
|
||||
playerStats[stat] = {'Volume': int(volume), 'Muted': 'muted' in muted}
|
||||
else:
|
||||
playerStats[stat] = {'Volume': int(playerStats[stat]), 'Muted': False}
|
||||
|
||||
if 'Connection Speed' == stat:
|
||||
speed, unit = playerStats[stat].split()
|
||||
|
||||
speedBytes = int(speed) * (1024 ** {'B/s': 0, 'KB/s': 1, 'MB/s': 2, 'GB/s': 3}[unit])
|
||||
|
||||
playerStats[stat] = {'Speed': int(speedBytes), 'Granularity': 's'}
|
||||
|
||||
if 'Network Activity' == stat:
|
||||
downString, upString = playerStats[stat].split(' / ')
|
||||
|
||||
down, downUnit = downString.replace('down', '').strip().split()
|
||||
up, upUnit = upString.replace('up', '').strip().split()
|
||||
|
||||
downBytes = int(down) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[downUnit])
|
||||
upBytes = int(up) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[upUnit])
|
||||
|
||||
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
|
||||
|
||||
if 'Total Transfered' == stat:
|
||||
downString, upString = playerStats[stat].split(' / ')
|
||||
|
||||
down, downUnit = downString.replace('down', '').strip().split()
|
||||
up, upUnit = upString.replace('up', '').strip().split()
|
||||
|
||||
downBytes = int(down) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[downUnit])
|
||||
upBytes = int(up) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[upUnit])
|
||||
|
||||
playerStats[stat] = {'Down': downBytes, 'Up': upBytes}
|
||||
|
||||
if 'Download Breakdown' == stat:
|
||||
server, peer = playerStats[stat].split(' - ')
|
||||
|
||||
server, serverUnit = server.replace('from servers', '').strip().split()
|
||||
peer, peerUnit = peer.replace('from peers', '').strip().split()
|
||||
|
||||
serverBytes = int(server) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[serverUnit])
|
||||
peerBytes = int(peer) * (1024 ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[peerUnit])
|
||||
|
||||
playerStats[stat] = {'Server': serverBytes, 'Peer': peerBytes}
|
||||
|
||||
if 'Buffer State' == stat:
|
||||
statsDict[stat] = statsDict[stat][1:-1].split(', ')
|
||||
del(playerStats[stat])
|
||||
|
||||
if 'Live Latency' == stat:
|
||||
latency, edge = playerStats[stat].split(' (from edge: ')
|
||||
latency = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in latency.replace('s', '').split('m') if part])))
|
||||
edge = sum(int(x) * 60 ** i for i, x in enumerate(reversed([part for part in edge.replace('s', '').replace(')', '').split('m') if part])))
|
||||
playerStats[stat] = {'Latency': latency, 'Edge': edge}
|
||||
|
||||
stats = {
|
||||
'player': playerStats,
|
||||
'peers': peersDict,
|
||||
'url': driver.current_url,
|
||||
'timestamp': int(time.time())
|
||||
}
|
||||
|
||||
# statsDict = {
|
||||
# 'userName': dict(
|
||||
# map(
|
||||
# lambda stat: (
|
||||
# stat.div.text,
|
||||
# stat.span.text.replace('\u21d3', 'down').replace('down/', 'down /').replace('\u21d1 ', 'up').replace('\u21d1', 'up').replace('\u00b7', '-').strip()
|
||||
# ), stats
|
||||
# )
|
||||
# )
|
||||
# }
|
||||
|
||||
statsDict.update({'Timestamp': time.strftime('%Y-%m-%dT%H:%M:%S%z')})
|
||||
statsDict['userName'] = 'user'
|
||||
|
||||
saveStats(statsDict)
|
||||
saveStats([stats])
|
||||
|
||||
def setupStats(driver: webdriver.Chrome, url: str):
|
||||
logger.log(logging.INFO, 'Setting up stats.')
|
||||
actions = ActionChains(driver)
|
||||
wait = WebDriverWait(driver, 30, poll_frequency=0.2)
|
||||
|
||||
@@ -99,12 +166,14 @@ def setupStats(driver: webdriver.Chrome, url: str):
|
||||
|
||||
wait.until(ec.presence_of_element_located((By.CLASS_NAME, 'vjs-big-play-button')))
|
||||
actions.click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
|
||||
actions.pause(2)
|
||||
actions.context_click(driver.find_element(By.CLASS_NAME ,'video-js')).perform()
|
||||
statsForNerds = driver.find_elements(By.CLASS_NAME ,'vjs-menu-item')
|
||||
actions.pause(2)
|
||||
actions.click(statsForNerds[-1]).perform()
|
||||
wait.until(ec.text_to_be_present_in_element((By.CLASS_NAME, 'vjs-stats-list'), 'Player'))
|
||||
|
||||
logger.log(logging.INFO, 'Stats setup complete.')
|
||||
|
||||
return driver
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -116,6 +185,10 @@ if __name__ == '__main__':
|
||||
|
||||
setupStats(driver, "https://tube.kobim.cloud/w/9hAbiwai4rsbw9QnPpPkCd")
|
||||
|
||||
schedule.every(1).seconds.do(downloadStats, driver)
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
logger.log(logging.INFO, 'Starting server collector.')
|
||||
httpd = HTTPServer(('localhost', 9092), partial(Handler, downloadStats, driver, logger))
|
||||
httpd.serve_forever()
|
||||
|
||||
#schedule.every(2).seconds.do(downloadStats, driver)
|
||||
#while True:
|
||||
#schedule.run_pending()
|
Reference in New Issue
Block a user