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