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 21enum { 22 MSG_INVALIDATE = 'ivdt' 23}; 24 25 26VideoView::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 65VideoView::~VideoView() 66{ 67 Settings::Default()->RemoveListener(&fGlobalSettingsListener); 68 delete fSubtitleBitmap; 69} 70 71 72void 73VideoView::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 96void 97VideoView::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*/ 122void 123VideoView::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 158void 159VideoView::MouseMoved(BPoint where, uint32 transit, 160 const BMessage* dragMessage) 161{ 162 fLastMouseMove = system_time(); 163} 164 165 166// #pragma mark - 167 168 169void 170VideoView::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 239void 240VideoView::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 249void 250VideoView::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 258void 259VideoView::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 266bool 267VideoView::UseOverlays() const 268{ 269 return fUseOverlays; 270} 271 272 273bool 274VideoView::IsOverlayActive() 275{ 276 bool active = false; 277 if (LockBitmap()) { 278 active = fOverlayMode; 279 UnlockBitmap(); 280 } 281 return active; 282} 283 284 285void 286VideoView::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 301void 302VideoView::SetPlaying(bool playing) 303{ 304 fIsPlaying = playing; 305} 306 307 308void 309VideoView::SetFullscreen(bool fullScreen) 310{ 311 fIsFullscreen = fullScreen; 312 fSendHideCounter = 0; 313 fFirstPulseAfterFullscreen = true; 314} 315 316 317void 318VideoView::SetFullscreenControlsVisible(bool visible) 319{ 320 fFullscreenControlsVisible = visible; 321 fSendHideCounter = 0; 322} 323 324 325void 326VideoView::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 342void 343VideoView::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 373void 374VideoView::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 390void 391VideoView::_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 402void 403VideoView::_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 416void 417VideoView::_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 444void 445VideoView::_SetOverlayMode(bool overlayMode) 446{ 447 fOverlayMode = overlayMode; 448 fSubtitleBitmap->SetOverlayMode(overlayMode); 449} 450 451 452void 453VideoView::_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