webRTC ts封装

import {
  ISuperNodeWebbrtc,
  IRTCOpstion,
  IUrlParams,
  IStreams,
  IRtcdnDraft,
  IParmUrl,
  AjaxResponse,
} from "@/typings/interface";

/* eslint-disable @typescript-eslint/no-explicit-any */
declare global {
  interface HTMLVideoElement {
    // eslint-disable-next-line @typescript-eslint/method-signature-style
    captureStream(frameRate?: number): MediaStream;
  }
}

/**
 * 兼容 ts 无法识别
 */
declare let MediaRecorder: any;
type MediaRecorders = InstanceType<typeof MediaRecorder>;

// const recordedBlobsArrs: any = []; // 视频录制流
/**
 * 播放器类
 */
export default class SUPERNODETSWEBRTC implements ISuperNodeWebbrtc {
  public elVideo: HTMLVideoElement; // 播放器 标签1
  public pc: RTCPeerConnection | null = null; // rtc pc 播放对象
  public opstion: IRTCOpstion; // 选项
  public mediaRecorder: MediaRecorders | null = null; // 录制视频
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  protected url: string; // 传入参数
  protected urlParams: IUrlParams; // url参数
  public recordedBlobsArrs: any = [];
  public constructor(
    url: string,
    elVideo: HTMLVideoElement,
    opstion?: IRTCOpstion
  ) {
    // 校验url地址是否正确
    if (!url.match(/^webrtc?:\/\//)) {
      throw "播放地址不正确 webrtc://xxxxxx";
    }
    this.url = url; // 用户输入url地址
    this.elVideo = elVideo; // video 标签
    this.opstion = opstion || {}; // rtc 服务器选项
    this.urlParams = this.ParseUrl(); // 拆解url参数
  }
  // 开始
  public Play(): Promise<RTCPeerConnection> {
    return new Promise((res, rej) => {
      this.pc = null;
      // 是否自动播放
      if (this.opstion.autoplay) {
        this.elVideo.setAttribute("autoplay", "autoplay");
      }
      this.StartLoading();
      if (this.pc) {
        res(this.pc);
      } else {
        rej(false);
      }
    });
  }
  // 暂停
  public Pause(): void {
    this.elVideo.pause();
  }
  // 暂停恢复
  public Resume(): void {
    this.elVideo.play();
  }
  public Stop(): void {
    console.log("stop");
  }
  // 停止播放并清理相关的播放资源(销毁)
  public Destroy(): void {
    this.Pause();
    if (this.pc) {
      this.pc.close();
      this.pc = null;
    }
  }
  // 只读,是否暂停播放
  public Paused(): void {
    console.log("Paused");
  }
  // 视频录制 webApi https://developer.mozilla.org/zh-CN/docs/Web/API/MediaStream_Recording_API
  public async RecordingStart(
    coding = "video/webm; codecs=vp9"
  ): Promise<void> {
    const stream = this.elVideo.captureStream();
    this.mediaRecorder = new MediaRecorder(stream, { mimeType: coding });
    this.mediaRecorder.ondataavailable = (event: any) => {
      console.log("录制成功:", event);
      if (event.data && event.data.size > 0) {
        this.recordedBlobsArrs.push(event.data);
      }
    };
    this.mediaRecorder.start();
  }
  // 视频录制播放
  public RecordingPaly(recordedVideo: HTMLVideoElement): void {
    const superBuffer = new Blob(this.recordedBlobsArrs, {
      type: "video/webm; codecs=vp9",
    });
    recordedVideo.src = "";
    recordedVideo.srcObject = null;
    recordedVideo.src = window.URL.createObjectURL(superBuffer);
    recordedVideo.controls = true;
    recordedVideo.setAttribute("autoplay", "autoplay");
    recordedVideo.play();
  }
  // 视频停止录制
  public RecordingStop(): void {
    if (this.mediaRecorder) {
      this.mediaRecorder.stop();
    }
  }
  // 下载视频
  public DownloadVideo(): void {
    const blob = new Blob(this.recordedBlobsArrs, {
      type: "video/webm; codecs=vp9",
    });
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.style.display = "none";
    a.href = url;
    a.download = "test.webm";
    document.body.appendChild(a);
    a.click();
    // 销毁
    setTimeout(() => {
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);
    }, 100);
  }
  // 开始加载视频
  protected StartLoading(): void {
    // 如果 pc有则关闭
    if (this.pc) {
      this.pc.close();
    }
    // webAPI 文档 https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/ontrack
    this.pc = new RTCPeerConnection(undefined); // 创建rtc 连接
    // 创建一个任务
    this.pc.ontrack = <T extends IStreams>(event: T) => {
      this.elVideo.srcObject = event.streams[0];
    };
    this.pc.addTransceiver("audio", { direction: "recvonly" });
    this.pc.addTransceiver("video", { direction: "recvonly" });
    this.pc
      .createOffer()
      .then((offer) => {
        if (this.pc) {
          return this.pc.setLocalDescription(offer).then(() => {
            return offer;
          });
        }
      })
      .then((offer) => {
        return new Promise((resolve, reject) => {
          const port: number = Number(this.urlParams.port) || 1985;
          // https://github.com/rtcdn/rtcdn-draft
          let api: string = this.urlParams.user_query.play || "/rtc/v1/play/";
          if (api.lastIndexOf("/") !== api.length - 1) {
            api += "/";
          }
          let url: string =
            "http://" + this.urlParams.server + ":" + port + api;
          for (const key in this.urlParams.user_query) {
            if (key !== "api" && key !== "play") {
              url += "&" + key + "=" + this.urlParams.user_query[key];
            }
          }
          // 请求参数
          const parm: IRtcdnDraft = {
            api: url,
            streamurl: this.urlParams.url,
            sdp: offer?.sdp,
            clientip: null,
          };
          setTimeout(() => {
            this.HttpPost(url, JSON.stringify(parm))
              .then((res) => {
                resolve(res.sdp);
              })
              .catch((err) => {
                reject(err);
              });
          }, 4000);
        });
      })
      .then((answer: any) => {
        if (this.pc) {
          // 替换 视频流服务器地址
          const newServerIP: string =
            this.opstion.replaceServerIP !== ""
              ? answer.replaceAll(
                  `${this.opstion.replaceServerIP}`,
                  this.opstion.replaceServerIPValue
                )
              : answer;
          return this.pc.setRemoteDescription(
            new RTCSessionDescription({ type: "answer", sdp: newServerIP })
          );
        }
      })
      .catch((err: any) => {
        throw err;
      });
  }
  // url 参数截取 &
  protected ParmUrl(url: string): IParmUrl | null {
    const query: string = url.split("?")[1]; // 截取 ? 后面的数据
    if (query) {
      const vars: string[] = query.split("&");
      const obj: IParmUrl = {};
      vars.forEach((item) => {
        const pair = item.split("=");
        obj[`${pair[0]}`] = pair[1];
      });
      return obj;
    } else {
      return null;
    }
  }
  // url 参数截取
  protected ParseUrl(): IUrlParams {
    const ret: IUrlParams = {
      app: "",
      port: "",
      schema: "",
      server: "",
      stream: "",
      url: this.url,
      user_query: {},
      vhost: "",
    };

    const urlNoSchema: string = this.url.slice(9, this.url.length - 1);
    const urlParmArr: string[] = urlNoSchema.split("/");
    const setProt = urlParmArr[0].split(":");
    ret.app = urlParmArr[1];
    ret.port = setProt.length >= 2 ? setProt[1] : "1985";
    ret.schema = this.url.substr(0, this.url.indexOf("://"));
    ret.server = setProt[0];
    ret.stream = urlParmArr[urlParmArr.length - 1];

    ret.user_query = this.ParmUrl(ret.stream) ? this.ParmUrl(ret.stream) : {};
    ret.vhost = ret.user_query.vhost;
    return ret;
  }
  /**
   * post 请求接口
   */
  protected HttpPost(url: string, parm: string): Promise<AjaxResponse> {
    return new Promise((resolve) => {
      let xhr: any = new XMLHttpRequest();
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
          const respone = JSON.parse(xhr.responseText);
          xhr = null;
          resolve(respone);
        }
      };
      xhr.open("POST", url, true);
      xhr.timeout = 5000; // 5 seconds for timeout
      xhr.responseType = "text";
      xhr.setRequestHeader("Content-Type", "application/json");
      xhr.send(parm);
    });
  }
}

webRTC 封装 SDK (由于年代久远,稍后补上完整代码)

posted @ 2024-11-13 15:58  半截肥皂  阅读(19)  评论(0编辑  收藏  举报