@@ -35,6 +35,9 @@ @interface FBMjpegServer()
3535@property (nonatomic , readonly ) FBImageProcessor *imageProcessor;
3636@property (nonatomic , readonly ) long long mainScreenID;
3737@property (nonatomic , assign ) NSUInteger consecutiveScreenshotFailures;
38+ @property (atomic , assign ) BOOL isStreaming;
39+ @property (nonatomic , assign ) NSUInteger sentFramesCount;
40+ @property (nonatomic , assign ) NSUInteger sentBytesCount;
3841
3942@end
4043
@@ -45,11 +48,15 @@ - (instancetype)init
4548{
4649 if ((self = [super init ])) {
4750 _consecutiveScreenshotFailures = 0 ;
51+ _isStreaming = YES ;
52+ _sentFramesCount = 0 ;
53+ _sentBytesCount = 0 ;
4854 _listeningClients = [NSMutableArray array ];
4955 dispatch_queue_attr_t queueAttributes = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL , QOS_CLASS_UTILITY , 0 );
5056 _backgroundQueue = dispatch_queue_create (QUEUE_NAME , queueAttributes);
57+ __weak typeof (self) weakSelf = self;
5158 dispatch_async (_backgroundQueue, ^{
52- [self streamScreenshot ];
59+ [weakSelf streamScreenshot ];
5360 });
5461 _imageProcessor = [[FBImageProcessor alloc ] init ];
5562 _mainScreenID = [XCUIScreen.mainScreen displayID ];
@@ -59,22 +66,29 @@ - (instancetype)init
5966
6067- (void )scheduleNextScreenshotWithInterval : (uint64_t )timerInterval timeStarted : (uint64_t )timeStarted
6168{
69+ if (!self.isStreaming ) {
70+ return ;
71+ }
6272 uint64_t timeElapsed = clock_gettime_nsec_np (CLOCK_MONOTONIC_RAW ) - timeStarted;
6373 int64_t nextTickDelta = timerInterval - timeElapsed;
74+ __weak typeof (self) weakSelf = self;
6475 if (nextTickDelta > 0 ) {
6576 dispatch_after (dispatch_time (DISPATCH_TIME_NOW , nextTickDelta), self.backgroundQueue , ^{
66- [self streamScreenshot ];
77+ [weakSelf streamScreenshot ];
6778 });
6879 } else {
6980 // Try to do our best to keep the FPS at a decent level
7081 dispatch_async (self.backgroundQueue , ^{
71- [self streamScreenshot ];
82+ [weakSelf streamScreenshot ];
7283 });
7384 }
7485}
7586
7687- (void )streamScreenshot
7788{
89+ if (!self.isStreaming ) {
90+ return ;
91+ }
7892 NSUInteger framerate = FBConfiguration.mjpegServerFramerate ;
7993 uint64_t timerInterval = (uint64_t )(1.0 / ((0 == framerate || framerate > MAX_FPS ) ? MAX_FPS : framerate) * NSEC_PER_SEC );
8094 uint64_t timeStarted = clock_gettime_nsec_np (CLOCK_MONOTONIC_RAW );
@@ -106,10 +120,11 @@ - (void)streamScreenshot
106120 self.consecutiveScreenshotFailures = 0 ;
107121
108122 CGFloat scalingFactor = FBConfiguration.mjpegScalingFactor / 100.0 ;
123+ __weak typeof (self) weakSelf = self;
109124 [self .imageProcessor submitImageData: screenshotData
110125 scalingFactor: scalingFactor
111126 completionHandler: ^(NSData * _Nonnull scaled) {
112- [self sendScreenshot: scaled];
127+ [weakSelf sendScreenshot: scaled];
113128 }];
114129
115130 [self scheduleNextScreenshotWithInterval: timerInterval timeStarted: timeStarted];
@@ -122,7 +137,17 @@ - (void)sendScreenshot:(NSData *)screenshotData {
122137 [chunk appendData: (id )[@" \r\n\r\n " dataUsingEncoding: NSUTF8StringEncoding]];
123138 @synchronized (self.listeningClients ) {
124139 for (GCDAsyncSocket *client in self.listeningClients ) {
125- [client writeData: chunk withTimeout: -1 tag: 0 ];
140+ // Slow clients should fail/close instead of buffering indefinitely.
141+ [client writeData: chunk withTimeout: FRAME_TIMEOUT tag: 0 ];
142+ }
143+ self.sentFramesCount ++;
144+ self.sentBytesCount += chunk.length * self.listeningClients .count ;
145+ NSUInteger framerate = MAX (1 , MIN (MAX_FPS , FBConfiguration.mjpegServerFramerate ));
146+ if (0 == self.sentFramesCount % framerate) {
147+ [FBLogger verboseLog: [NSString stringWithFormat: @" MJPEG stats: clients=%@ sentFrames=%@ sentBytes=%@ " ,
148+ @(self .listeningClients.count),
149+ @(self .sentFramesCount),
150+ @(self .sentBytesCount)]];
126151 }
127152 }
128153}
@@ -158,4 +183,22 @@ - (void)didClientDisconnect:(GCDAsyncSocket *)client
158183 [FBLogger log: @" Disconnected a client from screenshots broadcast" ];
159184}
160185
186+ - (void )stopStreaming
187+ {
188+ self.isStreaming = NO ;
189+ @synchronized (self.listeningClients ) {
190+ NSArray <GCDAsyncSocket *> *clients = self.listeningClients .copy ;
191+ [self .listeningClients removeAllObjects ];
192+ for (GCDAsyncSocket *client in clients) {
193+ [client disconnect ];
194+ }
195+ }
196+ }
197+
198+ - (void )dealloc
199+ {
200+ [self stopStreaming ];
201+ [FBLogger verboseLog: @" FBMjpegServer deallocated" ];
202+ }
203+
161204@end
0 commit comments