xref: /haiku/src/apps/mediaplayer/VideoView.cpp (revision a3e794ae459fec76826407f8ba8c94cd3535f128)
1 /*
2  * Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de>
3  * All rights reserved. Distributed under the terms of the MIT license.
4  */
5 
6 
7 #include "VideoView.h"
8 
9 #include <stdio.h>
10 
11 #include <Application.h>
12 #include <Bitmap.h>
13 #include <Region.h>
14 #include <Screen.h>
15 #include <WindowScreen.h>
16 
17 #include "Settings.h"
18 #include "SubtitleBitmap.h"
19 
20 
21 enum {
22 	MSG_INVALIDATE = 'ivdt'
23 };
24 
25 
26 VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask)
27 	:
28 	BView(frame, name, resizeMask, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE
29 		| B_PULSE_NEEDED),
30 	fVideoFrame(Bounds()),
31 	fOverlayMode(false),
32 	fIsPlaying(false),
33 	fIsFullscreen(false),
34 	fFullscreenControlsVisible(false),
35 	fFirstPulseAfterFullscreen(false),
36 	fSendHideCounter(0),
37 	fLastMouseMove(system_time()),
38 
39 	fSubtitleBitmap(new SubtitleBitmap),
40 	fSubtitleFrame(),
41 	fSubtitleMaxButtom(Bounds().bottom),
42 	fHasSubtitle(false),
43 	fSubtitleChanged(false),
44 
45 	fGlobalSettingsListener(this)
46 {
47 	SetViewColor(B_TRANSPARENT_COLOR);
48 	SetHighColor(0, 0, 0);
49 
50 	// create some hopefully sensible default overlay restrictions
51 	// they will be adjusted when overlays are actually used
52 	fOverlayRestrictions.min_width_scale = 0.25;
53 	fOverlayRestrictions.max_width_scale = 8.0;
54 	fOverlayRestrictions.min_height_scale = 0.25;
55 	fOverlayRestrictions.max_height_scale = 8.0;
56 
57 	Settings::Default()->AddListener(&fGlobalSettingsListener);
58 	_AdoptGlobalSettings();
59 
60 //SetSubTitle("<b><i>This</i></b> is a <font color=\"#00ff00\">test</font>!"
61 //	"\nWith a <i>short</i> line and a <b>long</b> line.");
62 }
63 
64 
65 VideoView::~VideoView()
66 {
67 	Settings::Default()->RemoveListener(&fGlobalSettingsListener);
68 	delete fSubtitleBitmap;
69 }
70 
71 
72 void
73 VideoView::Draw(BRect updateRect)
74 {
75 	BRegion outSideVideoRegion(updateRect);
76 
77 	if (LockBitmap()) {
78 		if (const BBitmap* bitmap = GetBitmap()) {
79 			outSideVideoRegion.Exclude(fVideoFrame);
80 			if (!fOverlayMode)
81 				_DrawBitmap(bitmap);
82 			else
83 				FillRect(fVideoFrame & updateRect, B_SOLID_LOW);
84 		}
85 		UnlockBitmap();
86 	}
87 
88 	if (outSideVideoRegion.CountRects() > 0)
89 		FillRegion(&outSideVideoRegion);
90 
91 	if (fHasSubtitle)
92 		_DrawSubtitle();
93 }
94 
95 
96 void
97 VideoView::MessageReceived(BMessage* message)
98 {
99 	switch (message->what) {
100 		case MSG_OBJECT_CHANGED:
101 			// received from fGlobalSettingsListener
102 			// TODO: find out which object, if we ever watch more than
103 			// the global settings instance...
104 			_AdoptGlobalSettings();
105 			break;
106 		case MSG_INVALIDATE:
107 		{
108 			BRect dirty;
109 			if (message->FindRect("dirty", &dirty) == B_OK)
110 				Invalidate(dirty);
111 			break;
112 		}
113 		default:
114 			BView::MessageReceived(message);
115 	}
116 }
117 
118 
119 /*!
120 	Disables the screen saver, and hides the full screen controls.
121 */
122 void
123 VideoView::Pulse()
124 {
125 	if (!fIsFullscreen || !fIsPlaying)
126 		return;
127 
128 	bigtime_t now = system_time();
129 	if (now - fLastMouseMove > 1500000) {
130 		fLastMouseMove = now;
131 		BPoint where;
132 		uint32 buttons;
133 		GetMouse(&where, &buttons, false);
134 		if (buttons == 0) {
135 			// Hide the full screen controls (and the mouse pointer)
136 			// after a while
137 			if (fFullscreenControlsVisible || fFirstPulseAfterFullscreen) {
138 				if (fSendHideCounter == 0 || fSendHideCounter == 3) {
139 					// Send after 1.5s and after 4.5s
140 					BMessage message(M_HIDE_FULL_SCREEN_CONTROLS);
141 					message.AddPoint("where", where);
142 					if (fSendHideCounter > 0)
143 						message.AddBool("force", true);
144 					Window()->PostMessage(&message, Window());
145 				}
146 				fSendHideCounter++;
147 				fFirstPulseAfterFullscreen = false;
148 			}
149 
150 			// Take care of disabling the screen saver
151 			ConvertToScreen(&where);
152 			set_mouse_position((int32)where.x, (int32)where.y);
153 		}
154 	}
155 }
156 
157 
158 void
159 VideoView::MouseMoved(BPoint where, uint32 transit,
160 	const BMessage* dragMessage)
161 {
162 	fLastMouseMove = system_time();
163 }
164 
165 
166 // #pragma mark -
167 
168 
169 void
170 VideoView::SetBitmap(const BBitmap* bitmap)
171 {
172 	VideoTarget::SetBitmap(bitmap);
173 	// Attention: Don't lock the window, if the bitmap is NULL. Otherwise
174 	// we're going to deadlock when the window tells the node manager to
175 	// stop the nodes (Window -> NodeManager -> VideoConsumer -> VideoView
176 	// -> Window).
177 	if (!bitmap || LockLooperWithTimeout(10000) != B_OK)
178 		return;
179 
180 	if (LockBitmap()) {
181 		if (fOverlayMode
182 			|| (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) != 0) {
183 			if (!fOverlayMode) {
184 				// init overlay
185 				rgb_color key;
186 				status_t ret = SetViewOverlay(bitmap, bitmap->Bounds(),
187 					fVideoFrame, &key, B_FOLLOW_ALL,
188 					B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL);
189 				if (ret == B_OK) {
190 					fOverlayKeyColor = key;
191 					SetLowColor(key);
192 					snooze(20000);
193 					FillRect(fVideoFrame, B_SOLID_LOW);
194 					Sync();
195 					// use overlay from here on
196 					_SetOverlayMode(true);
197 
198 					// update restrictions
199 					overlay_restrictions restrictions;
200 					if (bitmap->GetOverlayRestrictions(&restrictions) == B_OK)
201 						fOverlayRestrictions = restrictions;
202 				} else {
203 					// try again next time
204 					// synchronous draw
205 					FillRect(fVideoFrame);
206 					Sync();
207 				}
208 			} else {
209 				// transfer overlay channel
210 				rgb_color key;
211 				SetViewOverlay(bitmap, bitmap->Bounds(), fVideoFrame,
212 					&key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL
213 						| B_OVERLAY_FILTER_VERTICAL
214 						| B_OVERLAY_TRANSFER_CHANNEL);
215 			}
216 		} else if (fOverlayMode
217 			&& (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) == 0) {
218 			_SetOverlayMode(false);
219 			ClearViewOverlay();
220 			SetViewColor(B_TRANSPARENT_COLOR);
221 		}
222 		if (!fOverlayMode) {
223 			if (fSubtitleChanged) {
224 				_LayoutSubtitle();
225 				Invalidate(fVideoFrame | fSubtitleFrame);
226 			} else if (fHasSubtitle
227 				&& fVideoFrame.Intersects(fSubtitleFrame)) {
228 				Invalidate(fVideoFrame);
229 			} else
230 				_DrawBitmap(bitmap);
231 		}
232 
233 		UnlockBitmap();
234 	}
235 	UnlockLooper();
236 }
237 
238 
239 void
240 VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const
241 {
242 	*minScale = max_c(fOverlayRestrictions.min_width_scale,
243 		fOverlayRestrictions.min_height_scale);
244 	*maxScale = max_c(fOverlayRestrictions.max_width_scale,
245 		fOverlayRestrictions.max_height_scale);
246 }
247 
248 
249 void
250 VideoView::OverlayScreenshotPrepare()
251 {
252 	// TODO: Do nothing if the current bitmap is in RGB color space
253 	// and no overlay. Otherwise, convert current bitmap to RGB color
254 	// space an draw it in place of the normal display.
255 }
256 
257 
258 void
259 VideoView::OverlayScreenshotCleanup()
260 {
261 	// TODO: Do nothing if the current bitmap is in RGB color space
262 	// and no overlay. Otherwise clean view area with overlay color.
263 }
264 
265 
266 bool
267 VideoView::UseOverlays() const
268 {
269 	return fUseOverlays;
270 }
271 
272 
273 bool
274 VideoView::IsOverlayActive()
275 {
276 	bool active = false;
277 	if (LockBitmap()) {
278 		active = fOverlayMode;
279 		UnlockBitmap();
280 	}
281 	return active;
282 }
283 
284 
285 void
286 VideoView::DisableOverlay()
287 {
288 	if (!fOverlayMode)
289 		return;
290 
291 	FillRect(Bounds());
292 	Sync();
293 
294 	ClearViewOverlay();
295 	snooze(20000);
296 	Sync();
297 	_SetOverlayMode(false);
298 }
299 
300 
301 void
302 VideoView::SetPlaying(bool playing)
303 {
304 	fIsPlaying = playing;
305 }
306 
307 
308 void
309 VideoView::SetFullscreen(bool fullScreen)
310 {
311 	fIsFullscreen = fullScreen;
312 	fSendHideCounter = 0;
313 	fFirstPulseAfterFullscreen = true;
314 }
315 
316 
317 void
318 VideoView::SetFullscreenControlsVisible(bool visible)
319 {
320 	fFullscreenControlsVisible = visible;
321 	fSendHideCounter = 0;
322 }
323 
324 
325 void
326 VideoView::SetVideoFrame(const BRect& frame)
327 {
328 	if (fVideoFrame == frame)
329 		return;
330 
331 	BRegion invalid(fVideoFrame | frame);
332 	invalid.Exclude(frame);
333 	Invalidate(&invalid);
334 
335 	fVideoFrame = frame;
336 
337 	fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN));
338 	_LayoutSubtitle();
339 }
340 
341 
342 void
343 VideoView::SetSubTitle(const char* text)
344 {
345 	BRect oldSubtitleFrame = fSubtitleFrame;
346 
347 	if (text == NULL || text[0] == '\0') {
348 		fHasSubtitle = false;
349 		fSubtitleChanged = true;
350 	} else {
351 		fHasSubtitle = true;
352 		// If the subtitle frame still needs to be invalidated during
353 		// normal playback, make sure we don't unset the fSubtitleChanged
354 		// flag. It will be reset after drawing the subtitle once.
355 		fSubtitleChanged = fSubtitleBitmap->SetText(text) || fSubtitleChanged;
356 		if (fSubtitleChanged)
357 			_LayoutSubtitle();
358 	}
359 
360 	if (!fIsPlaying && Window() != NULL) {
361 		// If we are playing, the new subtitle will be displayed,
362 		// or the old one removed from screen, as soon as the next
363 		// frame is shown. Otherwise we need to invalidate manually.
364 		// But we are not in the window thread and we shall not lock
365 		// it or we may dead-locks.
366 		BMessage message(MSG_INVALIDATE);
367 		message.AddRect("dirty", oldSubtitleFrame | fSubtitleFrame);
368 		Window()->PostMessage(&message);
369 	}
370 }
371 
372 
373 void
374 VideoView::SetSubTitleMaxBottom(float bottom)
375 {
376 	if (bottom == fSubtitleMaxButtom)
377 		return;
378 
379 	fSubtitleMaxButtom = bottom;
380 
381 	BRect oldSubtitleFrame = fSubtitleFrame;
382 	_LayoutSubtitle();
383 	Invalidate(fSubtitleFrame | oldSubtitleFrame);
384 }
385 
386 
387 // #pragma mark -
388 
389 
390 void
391 VideoView::_DrawBitmap(const BBitmap* bitmap)
392 {
393 	SetDrawingMode(B_OP_COPY);
394 	uint32 options = B_WAIT_FOR_RETRACE;
395 	if (fUseBilinearScaling)
396 		options |= B_FILTER_BITMAP_BILINEAR;
397 
398 	DrawBitmapAsync(bitmap, bitmap->Bounds(), fVideoFrame, options);
399 }
400 
401 
402 void
403 VideoView::_DrawSubtitle()
404 {
405 	SetDrawingMode(B_OP_ALPHA);
406 	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
407 
408 	DrawBitmapAsync(fSubtitleBitmap->Bitmap(), fSubtitleFrame.LeftTop());
409 
410 	// Unless the subtitle frame intersects the video frame, we don't have
411 	// to draw the subtitle again.
412 	fSubtitleChanged = false;
413 }
414 
415 
416 void
417 VideoView::_AdoptGlobalSettings()
418 {
419 	mpSettings settings;
420 	Settings::Default()->Get(settings);
421 
422 	fUseOverlays = settings.useOverlays;
423 	fUseBilinearScaling = settings.scaleBilinear;
424 
425 	switch (settings.subtitleSize) {
426 		case mpSettings::SUBTITLE_SIZE_SMALL:
427 			fSubtitleBitmap->SetCharsPerLine(45.0);
428 			break;
429 		case mpSettings::SUBTITLE_SIZE_MEDIUM:
430 			fSubtitleBitmap->SetCharsPerLine(36.0);
431 			break;
432 		case mpSettings::SUBTITLE_SIZE_LARGE:
433 			fSubtitleBitmap->SetCharsPerLine(32.0);
434 			break;
435 	}
436 
437 	fSubtitlePlacement = settings.subtitlePlacement;
438 
439 	_LayoutSubtitle();
440 	Invalidate();
441 }
442 
443 
444 void
445 VideoView::_SetOverlayMode(bool overlayMode)
446 {
447 	fOverlayMode = overlayMode;
448 	fSubtitleBitmap->SetOverlayMode(overlayMode);
449 }
450 
451 
452 void
453 VideoView::_LayoutSubtitle()
454 {
455 	if (!fHasSubtitle)
456 		return;
457 
458 	const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap();
459 	if (subtitleBitmap == NULL)
460 		return;
461 
462 	fSubtitleFrame = subtitleBitmap->Bounds();
463 
464 	BPoint offset;
465 	offset.x = (fVideoFrame.left + fVideoFrame.right
466 		- fSubtitleFrame.Width()) / 2;
467 	switch (fSubtitlePlacement) {
468 		default:
469 		case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_VIDEO:
470 			offset.y = min_c(fSubtitleMaxButtom, fVideoFrame.bottom)
471 				- fSubtitleFrame.Height();
472 			break;
473 		case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_SCREEN:
474 		{
475 			// Center between video and screen bottom, if there is still
476 			// enough room.
477 			float centeredOffset = (fVideoFrame.bottom + fSubtitleMaxButtom
478 				- fSubtitleFrame.Height()) / 2;
479 			float maxOffset = fSubtitleMaxButtom - fSubtitleFrame.Height();
480 			offset.y = min_c(centeredOffset, maxOffset);
481 			break;
482 		}
483 	}
484 
485 	fSubtitleFrame.OffsetTo(offset);
486 }
487 
488 
489