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