xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #2995: video-stream.patch

File video-stream.patch, 7.9 KB (added by Antoine Martin, 3 months ago)

create a video stream URL

  • xpra/server/mixins/window_server.py

     
    11# -*- coding: utf-8 -*-
    22# This file is part of Xpra.
    3 # Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org>
     3# Copyright (C) 2010-2021 Antoine Martin <antoine@xpra.org>
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66#pylint: disable-msg=E1101
    77
     8from queue import Queue
     9
    810from xpra.util import typedict
     11from xpra.os_util import monotonic_time
    912from xpra.server.mixins.stub_server_mixin import StubServerMixin
    1013from xpra.server.source.windows_mixin import WindowsMixin
    1114from xpra.log import Logger
     
    1518metalog = Logger("metadata")
    1619geomlog = Logger("geometry")
    1720eventslog = Logger("events")
     21httplog = Logger("http")
    1822
    1923def noop(*_args):
    2024    pass
     
    8791        return {"windows" : self.get_windows_info(wids)}
    8892
    8993
     94    def get_http_scripts(self) -> dict:
     95        return {
     96            "/video" : self.handle_http_video_request,
     97            }
     98
     99    def handle_http_video_request(self, handler):
     100        from xpra.server.http_handler import parse_url
     101        args = parse_url(handler)
     102        httplog("handle_http_video_request(%s) args=%s", handler, args)
     103        wids = tuple(self._id_to_window.keys())
     104        if not wids:
     105            handler.send_error(500, "No windows to show")
     106            return
     107        try:
     108            wid = int(args.get("wid"))
     109            if wid not in wids:
     110                handler.send_error(500, "Invalid window ID")
     111                return
     112        except (ValueError, TypeError):
     113            if len(wids)==1:
     114                wid = wids[0]
     115            else:
     116                handler.send_error(500, "Please specify a window ID")
     117                return
     118        from xpra.server.window.window_video_source import WindowVideoSource
     119        window = self._id_to_window.get(wid)
     120        if not window:
     121            handler.send_error(500, "window disappeared")
     122            return
     123        wvs = []
     124        ww, wh = window.get_geometry()[2:4]
     125        record_congestion_event = noop
     126        encode_queue = Queue()
     127        def queue_size():
     128            return encode_queue.qsize()
     129        def encode_loop():
     130            httplog("encode_loop() starting for window %i", wid)
     131            while True:
     132                v = encode_queue.get()
     133                if v is None:
     134                    break
     135                fn, args = v
     136                fn(*args)
     137            httplog("encode_loop() ended for window %i", wid)
     138        def call_in_encode_thread(flag, fn, *args):
     139            #just queue it up:
     140            httplog("call_in_encode_thread(%s, %s) for window %i", flag, fn, wid)
     141            encode_queue.put((fn, args))
     142        def queue_packet(packet, wid, pixels, start_send, damage_packet_sent, fail_cb, flush):
     143            httplog("queue_packet%s", (packet, wid, pixels, start_send, damage_packet_sent, fail_cb, flush))
     144            start_send(0)
     145            damage_packet_sent(0)
     146            #unwrap Compressed object::
     147            data = packet[7]
     148            data = getattr(data, "data", data)
     149            #def ok(*_args):
     150            #    return True
     151            #from xpra.net.bytestreams import untilConcludes
     152            #untilConcludes(ok, ok, handler.wfile.write, data)
     153            try:
     154                handler.wfile.write(data)
     155                handler.wfile.flush()
     156                httplog("wrote %i bytes", len(data))
     157            except Exception:
     158                httplog("stopping on write error", exc_info=True)
     159                stop()
     160            #ack:
     161            width = packet[4]
     162            height = packet[5]
     163            damage_packet_sequence = packet[8]
     164            wvs[0].damage_packet_acked(damage_packet_sequence, width, height, 0, "")
     165        from xpra.server.source.source_stats import GlobalPerformanceStatistics
     166        statistics = GlobalPerformanceStatistics()
     167        from xpra.server.window.batch_config import DamageBatchConfig
     168        batch_config = DamageBatchConfig()
     169        auto_refresh_delay = 0
     170        av_sync = False
     171        av_sync_delay = 0
     172        from xpra.codecs.video_helper import getVideoHelper
     173        video_helper = getVideoHelper()
     174        server_core_encodings = server_encodings = ("h264", "h264+mp4", "h265", )
     175        encoding = args.get("encoding", "h264")
     176        if encoding not in server_encodings:
     177            raise Exception("invalid encoding specified: %r" % encoding)
     178        core_encodings = encodings = (encoding, )
     179        window_icon_encodings = ()
     180        encoding_options = typedict()
     181        csc_modes = {}
     182        for e in encodings:
     183            csc_modes[e] = ("BGRX", "BGR")
     184        encoding_options["full_csc_modes"] = csc_modes
     185        icons_encoding_options = typedict()
     186        rgb_formats = ("BGRX", "BGR", "RGB", "RGBX")
     187        default_encoding_options = typedict()
     188        mmap = False
     189        mmap_size = 0
     190        bandwidth_limit = 5000000
     191        jitter = 0
     192        #from now on, finish() will do nothing:
     193        finish = handler.finish
     194        def do_finish():
     195            handler.finish = finish
     196            try:
     197                finish()
     198            except Exception:
     199                log("error calling %s", finish, exc_info=True)
     200        handler.finish = noop
     201        def stop():
     202            encode_queue.put(None)
     203            do_finish()
     204        def send_frame():
     205            window = self._id_to_window.get(wid)
     206            if not window:
     207                httplog("window %i is gone, stopping video stream", wid)
     208                stop()
     209                return False
     210            if not wvs:
     211                try:
     212                    #instantiate the window video source from the UI thread:
     213                    wvs.append(WindowVideoSource(self.idle_add, self.timeout_add, self.source_remove,
     214                                                 ww, wh,
     215                                                 record_congestion_event, queue_size, call_in_encode_thread, queue_packet,
     216                                                 statistics,
     217                                                 wid, window, batch_config, auto_refresh_delay,
     218                                                 av_sync, av_sync_delay,
     219                                                 video_helper,
     220                                                 server_core_encodings, server_encodings,
     221                                                 encoding, encodings, core_encodings, window_icon_encodings,
     222                                                 encoding_options, icons_encoding_options,
     223                                                 rgb_formats,
     224                                                 default_encoding_options,
     225                                                 mmap, mmap_size, bandwidth_limit, jitter)
     226                                                 )
     227                except Exception as e:
     228                    log("failed to start video source", exc_info=True)
     229                    log.error("Error starting %s video stream:", encoding)
     230                    log.error(" %s", e)
     231                    stop()
     232                else:
     233                    from xpra.make_thread import start_thread
     234                    start_thread(encode_loop, "http-encode-loop", True)
     235            wvs[0].damage(0, 0, ww, wh)
     236            return handler.finish==noop  #pylint: disable=comparison-with-callable
     237        t = self.timeout_add(400, send_frame)
     238        now = monotonic_time()
     239        self.http_stream_check_timers[now] = (t, stop)
     240        handler.send_response(200)
     241        handler.send_header("Content-type", "video/%s" % encoding)
     242        handler.end_headers()
     243
     244
    90245    def parse_hello(self, ss, caps, send_ui):
    91246        if send_ui:
    92247            self.parse_hello_ui_window_settings(ss, caps)