gemercheung 3 年之前
父節點
當前提交
403e108ac3

+ 1 - 0
config.dev.yaml

@@ -10,6 +10,7 @@ log:
 queueConfig:
   move: 80
   rotate: 2
+  joystick: 5
 
 http:
   host: '0.0.0.0'

+ 1 - 0
config.production.yaml

@@ -6,6 +6,7 @@ app:
 queueConfig:
   move: 80
   rotate: 2
+  joystick: 5
 
 log:
   logFolder: /var/log/metaverse

+ 1 - 0
config.production1.yaml

@@ -9,6 +9,7 @@ log:
 queueConfig:
   move: 80
   rotate: 2
+  joystick: 5
 
 http:
   host: '0.0.0.0'

+ 2 - 0
src/app.module.ts

@@ -14,12 +14,14 @@ import {
 } from 'nest-winston';
 // import * as winston from 'winston';
 // import { join } from 'path';
+import { StreamModule } from './stream/stream.module';
 @Module({
   imports: [
     ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
     RoomModule,
     SceneModule,
     WinstonModule.forRoot(LoggerConfig),
+    StreamModule,
   ],
   controllers: [AppController],
   providers: [AppService, MetaGateway],

+ 9 - 9
src/move/move.service.ts

@@ -853,7 +853,7 @@ export class MoveService implements OnModuleInit {
     let count = 0;
     const neighPoints = [];
     //人在哪个角度
-    let _angle = this.getAngle(
+    const _angle = this.getAngle(
       breakPoint.position,
       { x: breakPoint.position.x + 1, y: breakPoint.position.y },
       playerPosition,
@@ -880,8 +880,8 @@ export class MoveService implements OnModuleInit {
         neighPoints.push(neighPoint);
         ++count;
       } else if (Math.abs(angle - move_angle) == 0) {
-        count = 2
-        console.log('handlejoysticktesttest:刚好45°的倍数')
+        count = 2;
+        console.log('handlejoysticktesttest:刚好45°的倍数');
         //debugger;
         break;
 
@@ -1011,7 +1011,7 @@ export class MoveService implements OnModuleInit {
 
         if (cameraInfo.mediaSrc) {
           this.reply.mediaSrc = cameraInfo.mediaSrc;
-        } 
+        }
         // if (this.cameraInfos.length == 0) {
         //   this.reply.moveOver = true;
         // }
@@ -1087,7 +1087,7 @@ export class MoveService implements OnModuleInit {
 
         if (cameraInfo.mediaSrc) {
           this.reply.mediaSrc = cameraInfo.mediaSrc;
-        } 
+        }
         // if (this.cameraInfos.length == 0) {
         //   this.reply.moveOver = true;
         // }
@@ -1096,7 +1096,7 @@ export class MoveService implements OnModuleInit {
           JSON.stringify(cameraInfo.camera_position),
         );
         user.camera.angle.yaw = cameraInfo.camera_angle.yaw;
-        
+
         this.sendingFrameForJoystick = true;
       }
       return this.reply;
@@ -1160,10 +1160,10 @@ export class MoveService implements OnModuleInit {
         Math.floor(user.camera.angle.yaw / 45);
       const moveFramesRes = await this.cacheService.get(key);
       const moveFrames = JSON.parse(moveFramesRes);
-      this.setCameraInfo(appId,moveFrames,breakPointId,chooseBreakPointId);
-      
+      this.setCameraInfo(appId, moveFrames, breakPointId, chooseBreakPointId);
+
       user.breakPointId = chooseBreakPointId;
-      
+
       const cameraInfo = this.getCameraInfo();
       if (cameraInfo != null) {
         this.reply['newUserStates'][0].playerState.camera.position =

+ 2 - 22
src/scene/scene.module.ts

@@ -2,24 +2,13 @@ import { Module, OnModuleInit } from '@nestjs/common';
 import { SceneService } from './scene.service';
 import { CacheModule } from '../cache/cache.module';
 import { CacheService } from '../cache/cache.service';
-// import { RedisService } from '../redis/redis.service';
 import { StreamService } from './stream/stream.service';
-// import { BullModule } from '@nestjs/bull';
-// import { RotateConsumer } from './rotate-consumer';
-// import { WalkingConsumer } from './walking-consumer';
 import { RotateService } from '../rotate/rotate.service';
 import { MoveService } from '../move/move.service';
 import { GetRouterService } from 'src/get-router/get-router.service';
-import { StreamWorkerService } from './stream-worker/stream-worker.service';
 @Module({
   imports: [
-    CacheModule,
-    // BullModule.registerQueue({
-    //   name: 'rotate',
-    // }),
-    // BullModule.registerQueue({
-    //   name: 'walking',
-    // }),
+    CacheModule
   ],
   controllers: [],
   providers: [
@@ -27,19 +16,10 @@ import { StreamWorkerService } from './stream-worker/stream-worker.service';
     CacheService,
     StreamService,
     RotateService,
-    // RotateConsumer,
-    // WalkingConsumer,
     MoveService,
     GetRouterService,
-    StreamWorkerService,
-  ],
-  exports: [
-    SceneService,
-    CacheService,
-    RotateService,
-    MoveService,
-    StreamWorkerService,
   ],
+  exports: [SceneService, CacheService, RotateService, MoveService],
 })
 export class SceneModule implements OnModuleInit {
   onModuleInit() {

+ 85 - 75
src/scene/scene.service.ts

@@ -26,7 +26,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
     private rotateService: RotateService,
     private moveService: MoveService,
     private getRouterService: GetRouterService, // @InjectQueue('rotate') private rotateQueue: Queue, // @InjectQueue('walking') private walkingQueue: Queue,
-  ) {}
+  ) { }
   @Client(grpcClientOptions) private readonly client: ClientGrpc;
 
   public _frameInteval: NodeJS.Timeout;
@@ -71,15 +71,17 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
   private roQueue: RxQueue = new DelayQueue(
     Number(this.configService.get('queueConfig.rotate')) || 20,
   );
-  private clickQueue: RxQueue = new DebounceQueue(500);
   private moveQueue: RxQueue = new DelayQueue(
     Number(this.configService.get('queueConfig.move')) || 20,
   );
-  private joystickQueue: RxQueue = new DebounceQueue(500);
+  // private joystickQueue: RxQueue = new DelayQueue(
+  //   Number(this.configService.get('queueConfig.joystick')) || 10,
+  // );
   private requestIFrameQueue: RxQueue = new DebounceQueue(2000);
 
   private requestIFrameQueueSub: any;
   private roRequestQueueSub: any;
+  private joystickQueueSub: any;
   private rotateTimeStamp: number;
   private rewalking = false;
   private firstRender = false;
@@ -97,14 +99,14 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
     clipPath: '',
     metaData: '',
   });
-  public lastMoveStreamFrameBk: StreamFrameType = {
-    frame: -1,
-    clipPath: '',
-    metaData: '',
-  };
+
+  private isJoystickHasStream = false;
+
 
   public users = {};
 
+  public sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
   onModuleInit(): void {
     this.sceneGrpcService =
       this.client.getService<SceneGrpcService>('SceneGrpcService');
@@ -267,16 +269,16 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
         const playerAngle = newUserStates.playerState.player.angle;
         this.logger.log(
           'stop-data-0' +
-            'trace_id: ' +
-            trace_id +
-            'userId:' +
-            userId +
-            'breakPointId :' +
-            breakPointId +
-            'cameraAngle :' +
-            JSON.stringify(cameraAngle) +
-            'playerAngle: ' +
-            JSON.stringify(playerAngle),
+          'trace_id: ' +
+          trace_id +
+          'userId:' +
+          userId +
+          'breakPointId :' +
+          breakPointId +
+          'cameraAngle :' +
+          JSON.stringify(cameraAngle) +
+          'playerAngle: ' +
+          JSON.stringify(playerAngle),
         );
         //debugger;
         console.log('moveService.stop-1:' + breakPointId);
@@ -325,7 +327,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
             const rounded = Number(inMillSeconds).toFixed(3);
             this.logger.log(
               `[timer]-rotate-入队列前: ${rounded}ms -->` +
-                JSON.stringify(stream),
+              JSON.stringify(stream),
             );
 
             this.roQueue.next(stream);
@@ -429,15 +431,15 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
       console.log('进入1 - searchRoad');
       this.logger.log(
         'handleWalking-users' +
-          JSON.stringify(this.moveService.users) +
-          ' this.user_id: ' +
-          this.user_id,
+        JSON.stringify(this.moveService.users) +
+        ' this.user_id: ' +
+        this.user_id,
       );
       this.logger.log(
         'handleWalking-currentUser' +
-          JSON.stringify(user) +
-          ' this.user_id: ' +
-          this.user_id,
+        JSON.stringify(user) +
+        ' this.user_id: ' +
+        this.user_id,
       );
       console.log('path-start' + user.breakPointId);
 
@@ -507,16 +509,16 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
         if (seqs?.length) {
           this.logger.log(
             'walking --队列总览:' +
-              ' 总段数: ' +
-              walkingRes.length +
-              ' 镜头帧数:' +
-              walkingRes[0].length +
-              ' 行走段数:' +
-              (walkingRes[0]?.length
-                ? walkingRes.length - 1
-                : walkingRes.length) +
-              ' 队列总帧数:' +
-              seqs.length,
+            ' 总段数: ' +
+            walkingRes.length +
+            ' 镜头帧数:' +
+            walkingRes[0].length +
+            ' 行走段数:' +
+            (walkingRes[0]?.length
+              ? walkingRes.length - 1
+              : walkingRes.length) +
+            ' 队列总帧数:' +
+            seqs.length,
           );
           const stop = performance.now();
           const inMillSeconds = stop - start;
@@ -572,7 +574,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
       const joystickRes = await this.moveService.seqExeJoystick(request);
       this.logger.log(
         'joystick-breakPointId:' +
-          this.moveService.users[this.user_id].breakPointId,
+        this.moveService.users[this.user_id].breakPointId,
       );
       // 有数据 [0]是rotate数据,[1-infinity]是walking数据
       this.logger.log('joystickRes-1', joystickRes);
@@ -588,11 +590,11 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
         //     ),
         console.log(
           'handlejoysticktesttest:' +
-            joystickRes.mediaSrc +
-            ',相机坐标:' +
-            JSON.stringify(
-              joystickRes.newUserStates[0].playerState.player.position,
-            ),
+          joystickRes.mediaSrc +
+          ',相机坐标:' +
+          JSON.stringify(
+            joystickRes.newUserStates[0].playerState.player.position,
+          ),
         );
         if (joystickRes.mediaSrc) {
           this.holdSteam();
@@ -615,16 +617,21 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
             DIR: setDIR,
           };
 
+          if (this.isJoystickHasStream) {
+            await this.sleep(10);
+          }
+
           const hasPush = await this.streamService.pushFrameToSteam(streamData);
           if (hasPush.done) {
+            this.isJoystickHasStream = true;
             console.log('joystick-hasPush', hasPush);
             this.moveService.sendingFrameForJoystick = false;
             const data = joystickRes as StreamReplyType;
-            console.log('handlejoystick-moveOver:'+data.moveOver)
+            console.log('handlejoystick-moveOver:' + data.moveOver);
             if (data?.moveOver && data.moveOver) {
               // moveOver
               console.log('回传updateUser', data);
-              const userId = this.user_id;
+              // const userId = this.user_id;
               // 回传点暂时有问题,待修复
               //const breakPointId = data.endBreakPointId || data.breakPointId;
               //const lastReply = JSON.stringify(joystickRes);
@@ -639,6 +646,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
               this.onJoysticking.next(false);
               this.resumeStream();
               this.joystickFrameCnt = -1;
+              this.isJoystickHasStream = false;
             }, 200);
           } else {
             console.error(
@@ -708,6 +716,9 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
       this.logger.error('joystick', error.message);
     }
   }
+
+
+
   /**
    * 主要处理moving的序列动作
    * @param seqs StreamReplyType[]
@@ -781,14 +792,14 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
           };
           this.logger.log(
             '[media-move]: ' +
-              ', moveframeCnt: ' +
-              this.moveframeCnt +
-              ', clipPath: ' +
-              stream.clipPath +
-              ', mType: ' +
-              stream.mType +
-              ', DIR: ' +
-              stream.DIR,
+            ', moveframeCnt: ' +
+            this.moveframeCnt +
+            ', clipPath: ' +
+            stream.clipPath +
+            ', mType: ' +
+            stream.mType +
+            ', DIR: ' +
+            stream.DIR,
             // stream.metaData,
           );
           this.logger.log(
@@ -984,8 +995,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
 
           const IDRflag = this._rotateCurrentFame % 5 === 0 ? 1 : 3;
           this.logger.log(
-            `当前rotate ,mainframeCnt:${this.frameCnt.getValue()}, _rotateCurrentFame:${
-              this._rotateCurrentFame
+            `当前rotate ,mainframeCnt:${this.frameCnt.getValue()}, _rotateCurrentFame:${this._rotateCurrentFame
             } IDRflag:${IDRflag}`,
           );
           stream.DIR = this.rotateFirstIDR ? 1 : IDRflag;
@@ -996,12 +1006,12 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
 
           this.logger.log(
             '[media-rotate]: ' +
-              ', frame: ' +
-              stream.frame +
-              ', rotateframeCnt: ' +
-              this.rotateframeCnt +
-              ', clipPath: ' +
-              stream.clipPath,
+            ', frame: ' +
+            stream.frame +
+            ', rotateframeCnt: ' +
+            this.rotateframeCnt +
+            ', clipPath: ' +
+            stream.clipPath,
             // stream.metaData,
           );
           // this.logger.log(
@@ -1063,12 +1073,12 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
           this.logger.log('frame', frame);
           console.log(
             'mock' +
-              ' maxMessageSize: ' +
-              this.channel.maxMessageSize() +
-              ' bytesReceived: ' +
-              this.peer.bytesReceived() +
-              ' bytesSent: ' +
-              this.peer.bytesSent(),
+            ' maxMessageSize: ' +
+            this.channel.maxMessageSize() +
+            ' bytesReceived: ' +
+            this.peer.bytesReceived() +
+            ' bytesSent: ' +
+            this.peer.bytesSent(),
           );
           if (frame === 1) {
             redisData = await this.rotateService.echo(this.user_id, true);
@@ -1083,7 +1093,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
                 delete redisData.mediaSrc;
                 this.logger.log(
                   `user:${this.user_id}:first render stream` +
-                    JSON.stringify({ path: clipPath, meta: redisData }),
+                  JSON.stringify({ path: clipPath, meta: redisData }),
                 );
                 const status = await this.pushFirstRender(
                   clipPath,
@@ -1111,15 +1121,15 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
 
             console.log(
               '空白流条件-->:' +
-                isOk +
-                ' onMoving: ' +
-                this.onMoving.value +
-                ' onRotating: ' +
-                this.onRotating.value +
-                ' onJoysticking: ' +
-                this.onJoysticking.value +
-                ' firstRender: ' +
-                this.firstRender,
+              isOk +
+              ' onMoving: ' +
+              this.onMoving.value +
+              ' onRotating: ' +
+              this.onRotating.value +
+              ' onJoysticking: ' +
+              this.onJoysticking.value +
+              ' firstRender: ' +
+              this.firstRender,
             );
           }
 

+ 0 - 4
src/scene/stream-worker/stream-worker.service.ts

@@ -1,4 +0,0 @@
-import { Injectable } from '@nestjs/common';
-
-@Injectable()
-export class StreamWorkerService {}

+ 97 - 0
src/stream/stream.d.ts

@@ -0,0 +1,97 @@
+interface StreamFrameType {
+  frame: number;
+  clipPath: string;
+  metaData: string;
+  serverTime?: number;
+  DIR?: number;
+  mediaSrc?: string; // 临时
+  mType?: string;
+  newUserStates?: NewUserStatesType[];
+  moveStart?: boolean;
+  moveOver?: boolean;
+}
+interface StreamMetaType {
+  frame: number;
+  metaData: string;
+}
+
+interface NewUserStatesType {
+  userId: string;
+  playerState: PlayerStateType;
+  renderInfo: RenderInfoType;
+  event: string | null;
+  relation: number;
+}
+
+interface PlayerStateType {
+  roomTypeId: string;
+  person: number;
+  avatarId: string;
+  skinId: string;
+  roomId: string;
+  isHost: boolean;
+  isFollowHost: boolean;
+  skinDataVersion: string;
+  avatarComponents: string;
+  nickName: string;
+  movingMode: number;
+  attitude: string;
+  areaName: string;
+  pathName: string;
+  pathId: string;
+  avatarSize: number;
+  extra: string;
+  prioritySync: boolean;
+  player: {
+    position: Point;
+    angle: Angle;
+  };
+  camera: {
+    position: Point;
+    angle: Angle;
+  };
+  cameraCenter: Point;
+}
+
+interface RenderInfoType {
+  renderType: number;
+  videoFrame: null | string;
+  cameraStateType: number;
+  isMoving: number;
+  needIfr: number;
+  isVideo: number;
+  stillFrame: number;
+  isRotating: number;
+  isFollowing: number;
+  clientPanoTitlesBitmap: any[];
+  clientPanoTreceId: string;
+  prefetchVideoId: string;
+  noMedia: boolean;
+}
+interface StreamReplyType {
+  traceIds: string[];
+  vehicle: string;
+  mediaSrc?: string;
+  newUserStates: NewUserStatesType[];
+  actionResponses: any[];
+  getStateType: number;
+  code: number;
+  msg: string;
+  // breakPointId?: number;
+  startBreakPointId?: number;
+  endBreakPointId?: number;
+  breakPointId?: number; //临时记录存在的点()
+  mType?: string; //类型
+  DIR: ?number;
+  moveStart?: boolean;
+  moveOver?: boolean;
+}
+
+// interface NewUserStatesType{
+
+// }
+
+interface StreamPushResponse {
+  frame: number;
+  done: boolean;
+}

+ 10 - 0
src/stream/stream.module.ts

@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+import { StreamService } from './stream.service';
+
+@Module({
+  imports: [],
+  controllers: [],
+  providers: [StreamService],
+  exports: [StreamService],
+})
+export class StreamModule {}

+ 5 - 5
src/scene/stream-worker/stream-worker.service.spec.ts

@@ -1,15 +1,15 @@
 import { Test, TestingModule } from '@nestjs/testing';
-import { StreamWorkerService } from './stream-worker.service';
+import { StreamService } from './stream.service';
 
-describe('StreamWorkerService', () => {
-  let service: StreamWorkerService;
+describe('StreamService', () => {
+  let service: StreamService;
 
   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
-      providers: [StreamWorkerService],
+      providers: [StreamService],
     }).compile();
 
-    service = module.get<StreamWorkerService>(StreamWorkerService);
+    service = module.get<StreamService>(StreamService);
   });
 
   it('should be defined', () => {

+ 228 - 0
src/stream/stream.service.ts

@@ -0,0 +1,228 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { DataChannel } from 'node-datachannel';
+// import * as path from 'path';
+import { existsSync, readFileSync } from 'fs';
+import * as streamBuffers from 'stream-buffers';
+import { BehaviorSubject } from 'rxjs';
+import { CacheService } from 'src/cache/cache.service';
+import { join } from 'path';
+
+@Injectable()
+export class StreamService {
+  private channel: DataChannel;
+  private readonly chunk_size = 16000;
+  private readonly block = 36;
+  private logger: Logger = new Logger('StreamService');
+  public onSteaming = new BehaviorSubject<boolean>(false);
+  public lastStreamFrame = new BehaviorSubject<StreamFrameType>({
+    frame: -1,
+    clipPath: '',
+    metaData: '',
+  });
+
+  // constructor() { }
+
+  setChannel(channel: DataChannel) {
+    this.channel = channel;
+  }
+  closeChannel() {
+    this.channel = null;
+  }
+  /**
+   * stream core push normal stream
+   * @param data meta Json
+   */
+
+  pushNormalDataToStream(data: any) {
+    const replyBin = JSON.stringify(data).replace(/\s/g, '');
+    const buff = Buffer.from(replyBin, 'utf-8');
+    if (this.channel && this.channel.isOpen()) {
+      this.channel.sendMessageBinary(buff);
+    }
+  }
+
+  /**
+   * stream core push with block meta stream
+   * @param data meta Json
+   */
+  pushMetaDataToSteam(stream: StreamMetaType): Promise<StreamPushResponse> {
+    return new Promise((resolve, reject) => {
+      try {
+        const metaData = stream.metaData;
+        const frame = stream.frame;
+        const metaDataBin = metaData.replace(/\s/g, '');
+        const buff = Buffer.from(metaDataBin, 'utf-8');
+
+        // const block = 36;
+        const blockBuff = Buffer.alloc(this.block);
+        const packBuffer = Buffer.concat([blockBuff, buff]);
+
+        const statusPack = new DataView(
+          packBuffer.buffer.slice(
+            packBuffer.byteOffset,
+            packBuffer.byteOffset + packBuffer.byteLength,
+          ),
+        );
+
+        statusPack.setUint32(0, 1437227610);
+        // 16 bit slot
+        statusPack.setUint16(6, this.block);
+        statusPack.setUint16(8, frame);
+        statusPack.setUint16(10, 255);
+        statusPack.setUint16(24, 0);
+        statusPack.setUint16(26, 0);
+
+        // 32 bit slot
+        statusPack.setUint32(12, buff.byteLength);
+        statusPack.setUint32(16, 0);
+        // statusPack.setUint32(20, 0);
+        statusPack.setUint32(24, 0);
+        statusPack.setUint32(28, 0);
+        statusPack.setUint32(32, 0);
+
+        if (this.channel && this.channel.isOpen()) {
+          const done = this.channel.sendMessageBinary(
+            Buffer.from(statusPack.buffer),
+          );
+          return resolve({ frame: stream.frame, done: done });
+        } else {
+          return resolve({ frame: stream.frame, done: false });
+        }
+      } catch (error) {
+        this.logger.error(error);
+        return reject({ frame: stream.frame, done: false });
+      }
+    });
+  }
+
+  /**
+   *  stream core push with block  stream
+   * @param stream   meta Json and stream
+   */
+
+  pushFrameToSteam(stream: StreamFrameType): Promise<StreamPushResponse> {
+    return new Promise((resolve, reject) => {
+      try {
+        // let start, stop;
+        const start = performance.now();
+        //TODO process.env 开发路径
+        let clipPath: string;
+        if (process.env.NODE_ENV === 'development') {
+          const src = stream.clipPath.replace('/mnt/metaverse/scene', '');
+          const srcTmp = join(__dirname, `../ws/${src}`);
+          clipPath = srcTmp;
+        } else {
+          clipPath = stream.clipPath;
+        }
+        // 增加不存在帧数据中断数据,原因有太多不准确的路径。
+        // 其次其他地方会拿这里的最后一帧数据会出错,由于上流数据很多不稳定问题尽可保流的稳定性。
+        if (!existsSync(clipPath)) {
+          this.logger.error('不存在的推流路径::' + clipPath);
+          return resolve({ frame: stream.frame, done: false });
+        }
+        // const clipPath = stream.clipPath;
+        const metaData = stream.metaData || '{}';
+        const frame = stream.frame;
+        const serverTime = stream.serverTime || 754871824;
+        const dir = stream.DIR || 1;
+
+        this.lastStreamFrame.next({
+          clipPath: stream.clipPath,
+          metaData: metaData,
+          frame: frame,
+          serverTime: serverTime,
+          DIR: dir,
+        });
+
+        const metaDataString = metaData.replace(/\s/g, '');
+        const coordBuff = Buffer.from(metaDataString, 'utf-8');
+        // console.warn('coordBuff', coordBuff.byteLength);
+        // const steamStat = statSync(clipPath);
+        // const steamTotalSize = metaData.length + steamStat.size;
+
+        const clipBuffer = readFileSync(clipPath);
+
+        const steam = new streamBuffers.ReadableStreamBuffer({
+          frequency: 1, // in milliseconds.
+          chunkSize: this.chunk_size - this.block, // in bytes.
+        });
+        steam.put(coordBuff);
+        steam.put(clipBuffer);
+
+        let steamByteLength = 0;
+
+        steam.on('data', (data: Buffer) => {
+          this.onSteaming.next(true);
+
+          // this.logger.log('data', data, data.byteLength);
+          const blockBuffStart = Buffer.alloc(this.block);
+          const packBuffer = Buffer.concat([blockBuffStart, data]);
+
+          const framePack = new DataView(
+            packBuffer.buffer.slice(
+              packBuffer.byteOffset,
+              packBuffer.byteOffset + packBuffer.byteLength,
+            ),
+          );
+
+          // 16 bit slot
+          // framePack.setUint32(4)
+          framePack.setUint16(6, this.block);
+          framePack.setUint16(8, frame); // first render cnt
+          framePack.setUint16(10, dir); // isDIR
+          framePack.setUint16(24, 0);
+          framePack.setUint16(26, 0);
+
+          // 32 bit slot
+          // statusPack.setUint32(12, buff.byteLength);
+          // this.logger.log('metaLen', coordBuff.byteLength);
+          // this.logger.log('metaLen', clipBuffer.byteLength);
+
+          framePack.setUint32(0, 1437227610);
+          framePack.setUint32(12, coordBuff.byteLength); // metaLen
+          framePack.setUint32(16, clipBuffer.byteLength); // mediaLen
+          framePack.setUint32(20, serverTime); //server_time
+          framePack.setUint32(24, 0);
+          framePack.setUint32(28, 0);
+          framePack.setUint32(this.block - 4, steamByteLength);
+          const isLastFrame = framePack.byteLength - this.chunk_size < 0;
+
+          // this.logger.log('statusPack', statusPack);
+          if (this.channel && this.channel.isOpen()) {
+            this.channel.sendMessageBinary(Buffer.from(framePack.buffer));
+          }
+          steamByteLength += data.byteLength;
+          if (isLastFrame) {
+            // this.logger.log('isLastFrame', isLastFrame);
+            // steamByteLength = 0;
+            // this.onSteaming.next(false);
+            steam.stop();
+          }
+        });
+        //TODO steam can't trigger end
+        steam.on('end', () => {
+          steamByteLength = 0;
+          // this.logger.log('stream end');
+          const stop = performance.now();
+          const inMillSeconds = stop - start;
+          const rounded = Number(inMillSeconds).toFixed(3);
+          this.logger.log(
+            `[timer]-当前流:${stream.clipPath}流耗时-->${rounded}ms`,
+          );
+          if (this.onSteaming.value) {
+            this.onSteaming.next(false);
+          }
+
+          return resolve({ frame: stream.frame, done: true });
+        });
+        steam.on('error', (error) => {
+          this.logger.error('steam-error', error.message);
+          return reject({ frame: stream.frame, done: false });
+        });
+      } catch (error) {
+        this.logger.error(error);
+        return reject({ frame: stream.frame, done: false });
+      }
+    });
+  }
+}