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