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