xref: /haiku/src/apps/mediaplayer/VideoView.cpp (revision fc75f2df0c666dcc61be83c4facdd3132340c2fb)
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 | B_OVERLAY_FILTER_VERTICAL);
172 				if (ret == B_OK) {
173 					fOverlayKeyColor = key;
174 					SetLowColor(key);
175 					snooze(20000);
176 					FillRect(fVideoFrame, B_SOLID_LOW);
177 					Sync();
178 					// use overlay from here on
179 					_SetOverlayMode(true);
180 
181 					// update restrictions
182 					overlay_restrictions restrictions;
183 					if (bitmap->GetOverlayRestrictions(&restrictions) == B_OK)
184 						fOverlayRestrictions = restrictions;
185 				} else {
186 					// try again next time
187 					// synchronous draw
188 					FillRect(fVideoFrame);
189 					Sync();
190 				}
191 			} else {
192 				// transfer overlay channel
193 				rgb_color key;
194 				SetViewOverlay(bitmap, bitmap->Bounds(), fVideoFrame,
195 					&key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL
196 						| B_OVERLAY_FILTER_VERTICAL
197 						| B_OVERLAY_TRANSFER_CHANNEL);
198 			}
199 		} else if (fOverlayMode
200 			&& (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) == 0) {
201 			_SetOverlayMode(false);
202 			ClearViewOverlay();
203 			SetViewColor(B_TRANSPARENT_COLOR);
204 		}
205 		if (!fOverlayMode) {
206 			if (fSubtitleChanged) {
207 				_LayoutSubtitle();
208 				Invalidate(fVideoFrame | fSubtitleFrame);
209 			} else if (fHasSubtitle
210 				&& fVideoFrame.Intersects(fSubtitleFrame)) {
211 				Invalidate(fVideoFrame);
212 			} else
213 				_DrawBitmap(bitmap);
214 		}
215 
216 		UnlockBitmap();
217 	}
218 	UnlockLooper();
219 }
220 
221 
222 void
223 VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const
224 {
225 	*minScale = max_c(fOverlayRestrictions.min_width_scale,
226 		fOverlayRestrictions.min_height_scale);
227 	*maxScale = max_c(fOverlayRestrictions.max_width_scale,
228 		fOverlayRestrictions.max_height_scale);
229 }
230 
231 
232 void
233 VideoView::OverlayScreenshotPrepare()
234 {
235 	// TODO: Do nothing if the current bitmap is in RGB color space
236 	// and no overlay. Otherwise, convert current bitmap to RGB color
237 	// space an draw it in place of the normal display.
238 }
239 
240 
241 void
242 VideoView::OverlayScreenshotCleanup()
243 {
244 	// TODO: Do nothing if the current bitmap is in RGB color space
245 	// and no overlay. Otherwise clean view area with overlay color.
246 }
247 
248 
249 bool
250 VideoView::UseOverlays() const
251 {
252 	return fUseOverlays;
253 }
254 
255 
256 bool
257 VideoView::IsOverlayActive()
258 {
259 	bool active = false;
260 	if (LockBitmap()) {
261 		active = fOverlayMode;
262 		UnlockBitmap();
263 	}
264 	return active;
265 }
266 
267 
268 void
269 VideoView::DisableOverlay()
270 {
271 	if (!fOverlayMode)
272 		return;
273 
274 	FillRect(Bounds());
275 	Sync();
276 
277 	ClearViewOverlay();
278 	snooze(20000);
279 	Sync();
280 	_SetOverlayMode(false);
281 }
282 
283 
284 void
285 VideoView::SetPlaying(bool playing)
286 {
287 	fIsPlaying = playing;
288 }
289 
290 
291 void
292 VideoView::SetFullscreen(bool fullScreen)
293 {
294 	fIsFullscreen = fullScreen;
295 }
296 
297 
298 void
299 VideoView::SetVideoFrame(const BRect& frame)
300 {
301 	if (fVideoFrame == frame)
302 		return;
303 
304 	BRegion invalid(fVideoFrame | frame);
305 	invalid.Exclude(frame);
306 	Invalidate(&invalid);
307 
308 	fVideoFrame = frame;
309 
310 	fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN));
311 	_LayoutSubtitle();
312 }
313 
314 
315 void
316 VideoView::SetSubTitle(const char* text)
317 {
318 	BRect oldSubtitleFrame = fSubtitleFrame;
319 
320 	if (text == NULL || text[0] == '\0') {
321 		fHasSubtitle = false;
322 		fSubtitleChanged = true;
323 	} else {
324 		fHasSubtitle = true;
325 		// If the subtitle frame still needs to be invalidated during
326 		// normal playback, make sure we don't unset the fSubtitleChanged
327 		// flag. It will be reset after drawing the subtitle once.
328 		fSubtitleChanged = fSubtitleBitmap->SetText(text) || fSubtitleChanged;
329 		if (fSubtitleChanged)
330 			_LayoutSubtitle();
331 	}
332 
333 	if (!fIsPlaying && Window() != NULL) {
334 		// If we are playing, the new subtitle will be displayed,
335 		// or the old one removed from screen, as soon as the next
336 		// frame is shown. Otherwise we need to invalidate manually.
337 		// But we are not in the window thread and we shall not lock
338 		// it or we may dead-locks.
339 		BMessage message(MSG_INVALIDATE);
340 		message.AddRect("dirty", oldSubtitleFrame | fSubtitleFrame);
341 		Window()->PostMessage(&message);
342 	}
343 }
344 
345 
346 void
347 VideoView::SetSubTitleMaxBottom(float bottom)
348 {
349 	if (bottom == fSubtitleMaxButtom)
350 		return;
351 
352 	fSubtitleMaxButtom = bottom;
353 
354 	BRect oldSubtitleFrame = fSubtitleFrame;
355 	_LayoutSubtitle();
356 	Invalidate(fSubtitleFrame | oldSubtitleFrame);
357 }
358 
359 
360 // #pragma mark -
361 
362 
363 void
364 VideoView::_DrawBitmap(const BBitmap* bitmap)
365 {
366 	SetDrawingMode(B_OP_COPY);
367 	uint32 options = B_WAIT_FOR_RETRACE;
368 	if (fUseBilinearScaling)
369 		options |= B_FILTER_BITMAP_BILINEAR;
370 
371 	DrawBitmapAsync(bitmap, bitmap->Bounds(), fVideoFrame, options);
372 }
373 
374 
375 void
376 VideoView::_DrawSubtitle()
377 {
378 	SetDrawingMode(B_OP_ALPHA);
379 	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
380 
381 	DrawBitmapAsync(fSubtitleBitmap->Bitmap(), fSubtitleFrame.LeftTop());
382 
383 	// Unless the subtitle frame intersects the video frame, we don't have
384 	// to draw the subtitle again.
385 	fSubtitleChanged = false;
386 }
387 
388 
389 void
390 VideoView::_AdoptGlobalSettings()
391 {
392 	mpSettings settings;
393 	Settings::Default()->Get(settings);
394 
395 	fUseOverlays = settings.useOverlays;
396 	fUseBilinearScaling = settings.scaleBilinear;
397 
398 	switch (settings.subtitleSize) {
399 		case mpSettings::SUBTITLE_SIZE_SMALL:
400 			fSubtitleBitmap->SetCharsPerLine(45.0);
401 			break;
402 		case mpSettings::SUBTITLE_SIZE_MEDIUM:
403 			fSubtitleBitmap->SetCharsPerLine(36.0);
404 			break;
405 		case mpSettings::SUBTITLE_SIZE_LARGE:
406 			fSubtitleBitmap->SetCharsPerLine(32.0);
407 			break;
408 	}
409 
410 	fSubtitlePlacement = settings.subtitlePlacement;
411 
412 	_LayoutSubtitle();
413 	Invalidate();
414 }
415 
416 
417 void
418 VideoView::_SetOverlayMode(bool overlayMode)
419 {
420 	fOverlayMode = overlayMode;
421 	fSubtitleBitmap->SetOverlayMode(overlayMode);
422 }
423 
424 
425 void
426 VideoView::_LayoutSubtitle()
427 {
428 	if (!fHasSubtitle)
429 		return;
430 
431 	const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap();
432 	if (subtitleBitmap == NULL)
433 		return;
434 
435 	fSubtitleFrame = subtitleBitmap->Bounds();
436 
437 	BPoint offset;
438 	offset.x = (fVideoFrame.left + fVideoFrame.right
439 		- fSubtitleFrame.Width()) / 2;
440 	switch (fSubtitlePlacement) {
441 		default:
442 		case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_VIDEO:
443 			offset.y = min_c(fSubtitleMaxButtom, fVideoFrame.bottom)
444 				- fSubtitleFrame.Height();
445 			break;
446 		case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_SCREEN:
447 		{
448 			// Center between video and screen bottom, if there is still
449 			// enough room.
450 			float centeredOffset = (fVideoFrame.bottom + fSubtitleMaxButtom
451 				- fSubtitleFrame.Height()) / 2;
452 			float maxOffset = fSubtitleMaxButtom - fSubtitleFrame.Height();
453 			offset.y = min_c(centeredOffset, maxOffset);
454 			break;
455 		}
456 	}
457 
458 	fSubtitleFrame.OffsetTo(offset);
459 }
460 
461 
462