@@ -3,6 +3,7 @@ package common
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "encoding/base64"
6
7
"encoding/json"
7
8
"errors"
8
9
"fmt"
@@ -229,6 +230,9 @@ type Page struct {
229
230
closedMu sync.RWMutex
230
231
closed bool
231
232
233
+ videoCaptureMu sync.RWMutex
234
+ videoCapture * videocapture
235
+
232
236
// TODO: setter change these fields (mutex?)
233
237
emulatedSize * EmulatedSize
234
238
mediaType MediaType
@@ -334,6 +338,7 @@ func (p *Page) initEvents() {
334
338
335
339
events := []string {
336
340
cdproto .EventRuntimeConsoleAPICalled ,
341
+ cdproto .EventPageScreencastFrame ,
337
342
}
338
343
p .session .on (p .ctx , events , p .eventCh )
339
344
@@ -356,8 +361,17 @@ func (p *Page) initEvents() {
356
361
"sid:%v tid:%v" , p .session .ID (), p .targetID )
357
362
return
358
363
case event := <- p .eventCh :
359
- if ev , ok := event .data .(* cdpruntime.EventConsoleAPICalled ); ok {
360
- p .onConsoleAPICalled (ev )
364
+ p .logger .Debugf ("Page:initEvents:event" ,
365
+ "sid:%v tid:%v event:%s eventDataType:%T" , p .session .ID (), p .targetID , event .typ , event .data )
366
+ switch event .typ {
367
+ case cdproto .EventPageScreencastFrame :
368
+ if ev , ok := event .data .(* page.EventScreencastFrame ); ok {
369
+ p .onScreencastFrame (ev )
370
+ }
371
+ case cdproto .EventRuntimeConsoleAPICalled :
372
+ if ev , ok := event .data .(* cdpruntime.EventConsoleAPICalled ); ok {
373
+ p .onConsoleAPICalled (ev )
374
+ }
361
375
}
362
376
}
363
377
}
@@ -1091,6 +1105,67 @@ func (p *Page) Screenshot(opts *PageScreenshotOptions, sp ScreenshotPersister) (
1091
1105
return buf , err
1092
1106
}
1093
1107
1108
+ // CaptureVideo will start a screen cast of the current page and save it to specified file.
1109
+ func (p * Page ) CaptureVideo (opts * VideoCaptureOptions , scp VideoCapturePersister ) error {
1110
+ p .videoCaptureMu .RLock ()
1111
+ defer p .videoCaptureMu .RUnlock ()
1112
+
1113
+ if p .videoCapture != nil {
1114
+ return fmt .Errorf ("ongoing video capture" )
1115
+ }
1116
+
1117
+ vc , err := newVideoCapture (p .ctx , p .logger , * opts , scp )
1118
+ if err != nil {
1119
+ return fmt .Errorf ("creating video capture: %w" , err )
1120
+ }
1121
+ p .videoCapture = vc
1122
+
1123
+ err = p .session .ExecuteWithoutExpectationOnReply (
1124
+ p .ctx ,
1125
+ cdppage .CommandStartScreencast ,
1126
+ cdppage.StartScreencastParams {
1127
+ Format : "png" ,
1128
+ Quality : opts .Quality ,
1129
+ MaxWidth : opts .MaxWidth ,
1130
+ MaxHeight : opts .MaxHeight ,
1131
+ EveryNthFrame : opts .EveryNthFrame ,
1132
+ },
1133
+ nil ,
1134
+ )
1135
+ if err != nil {
1136
+ return fmt .Errorf ("starting screen cast %w" , err )
1137
+ }
1138
+
1139
+ return nil
1140
+ }
1141
+
1142
+ // StopVideCapture stops any ongoing screen capture. In none is ongoing, is nop
1143
+ func (p * Page ) StopVideCapture () error {
1144
+ p .videoCaptureMu .RLock ()
1145
+ defer p .videoCaptureMu .RUnlock ()
1146
+
1147
+ if p .videoCapture == nil {
1148
+ return nil
1149
+ }
1150
+
1151
+ err := p .session .ExecuteWithoutExpectationOnReply (
1152
+ p .ctx ,
1153
+ cdppage .CommandStopScreencast ,
1154
+ nil ,
1155
+ nil ,
1156
+ )
1157
+ // don't return error to allow video to be recorded
1158
+ if err != nil {
1159
+ p .logger .Errorf ("Page:StopVideoCapture" , "sid:%v error:%v" , p .sessionID (), err )
1160
+ }
1161
+
1162
+ // prevent any pending frame to be sent to video capture while closing it
1163
+ vc := p .videoCapture
1164
+ p .videoCapture = nil
1165
+
1166
+ return vc .Close (p .ctx )
1167
+ }
1168
+
1094
1169
func (p * Page ) SelectOption (selector string , values goja.Value , opts goja.Value ) []string {
1095
1170
p .logger .Debugf ("Page:SelectOption" , "sid:%v selector:%s" , p .sessionID (), selector )
1096
1171
@@ -1294,6 +1369,41 @@ func (p *Page) TargetID() string {
1294
1369
return p .targetID .String ()
1295
1370
}
1296
1371
1372
+ func (p * Page ) onScreencastFrame (event * page.EventScreencastFrame ) {
1373
+ p .videoCaptureMu .RLock ()
1374
+ defer p .videoCaptureMu .RUnlock ()
1375
+
1376
+ if p .videoCapture != nil {
1377
+ err := p .session .ExecuteWithoutExpectationOnReply (
1378
+ p .ctx ,
1379
+ cdppage .CommandScreencastFrameAck ,
1380
+ cdppage.ScreencastFrameAckParams {SessionID : event .SessionID },
1381
+ nil ,
1382
+ )
1383
+ if err != nil {
1384
+ p .logger .Debugf ("Page:onScreenCastFrame" , "frame ack:%v" , err )
1385
+ return
1386
+ }
1387
+
1388
+ frameData := make ([]byte , base64 .StdEncoding .DecodedLen (len (event .Data )))
1389
+ _ , err = base64 .StdEncoding .Decode (frameData , []byte (event .Data ))
1390
+ if err != nil {
1391
+ p .logger .Debugf ("Page:onScreenCastFrame" , "decoding frame :%v" , err )
1392
+ }
1393
+ //content := base64.NewDecoder(base64.StdEncoding, bytes.NewBuffer([]byte(event.Data)))
1394
+ err = p .videoCapture .handleFrame (
1395
+ p .ctx ,
1396
+ & VideoFrame {
1397
+ Content : frameData ,
1398
+ Timestamp : event .Metadata .Timestamp .Time ().UnixMilli (),
1399
+ },
1400
+ )
1401
+ if err != nil {
1402
+ p .logger .Debugf ("Page:onScreenCastFrame" , "handling frame :%v" , err )
1403
+ }
1404
+ }
1405
+ }
1406
+
1297
1407
func (p * Page ) onConsoleAPICalled (event * cdpruntime.EventConsoleAPICalled ) {
1298
1408
// If there are no handlers for EventConsoleAPICalled, return
1299
1409
p .eventHandlersMu .RLock ()
0 commit comments