1 #include "components/VideoVlcComponent.h"
2 
3 #include "renderers/Renderer.h"
4 #include "resources/TextureResource.h"
5 #include "utils/StringUtil.h"
6 #include "PowerSaver.h"
7 #include "Settings.h"
8 #include <vlc/vlc.h>
9 #include <SDL_mutex.h>
10 
11 #ifdef WIN32
12 #include <codecvt>
13 #endif
14 
15 libvlc_instance_t* VideoVlcComponent::mVLC = NULL;
16 
17 // VLC prepares to render a video frame.
lock(void * data,void ** p_pixels)18 static void *lock(void *data, void **p_pixels) {
19 	struct VideoContext *c = (struct VideoContext *)data;
20 	SDL_LockMutex(c->mutex);
21 	SDL_LockSurface(c->surface);
22 	*p_pixels = c->surface->pixels;
23 	return NULL; // Picture identifier, not needed here.
24 }
25 
26 // VLC just rendered a video frame.
unlock(void * data,void *,void * const *)27 static void unlock(void *data, void* /*id*/, void *const* /*p_pixels*/) {
28 	struct VideoContext *c = (struct VideoContext *)data;
29 	SDL_UnlockSurface(c->surface);
30 	SDL_UnlockMutex(c->mutex);
31 }
32 
33 // VLC wants to display a video frame.
display(void *,void *)34 static void display(void* /*data*/, void* /*id*/) {
35 	//Data to be displayed
36 }
37 
VideoVlcComponent(Window * window,std::string subtitles)38 VideoVlcComponent::VideoVlcComponent(Window* window, std::string subtitles) :
39 	VideoComponent(window),
40 	mMediaPlayer(nullptr)
41 {
42 	memset(&mContext, 0, sizeof(mContext));
43 
44 	// Get an empty texture for rendering the video
45 	mTexture = TextureResource::get("");
46 
47 	// Make sure VLC has been initialised
48 	setupVLC(subtitles);
49 }
50 
~VideoVlcComponent()51 VideoVlcComponent::~VideoVlcComponent()
52 {
53 	stopVideo();
54 }
55 
setResize(float width,float height)56 void VideoVlcComponent::setResize(float width, float height)
57 {
58 	mTargetSize = Vector2f(width, height);
59 	mTargetIsMax = false;
60 	mStaticImage.setResize(width, height);
61 	resize();
62 }
63 
setMaxSize(float width,float height)64 void VideoVlcComponent::setMaxSize(float width, float height)
65 {
66 	mTargetSize = Vector2f(width, height);
67 	mTargetIsMax = true;
68 	mStaticImage.setMaxSize(width, height);
69 	resize();
70 }
71 
resize()72 void VideoVlcComponent::resize()
73 {
74 	if(!mTexture)
75 		return;
76 
77 	const Vector2f textureSize((float)mVideoWidth, (float)mVideoHeight);
78 
79 	if(textureSize == Vector2f::Zero())
80 		return;
81 
82 		// SVG rasterization is determined by height (see SVGResource.cpp), and rasterization is done in terms of pixels
83 		// if rounding is off enough in the rasterization step (for images with extreme aspect ratios), it can cause cutoff when the aspect ratio breaks
84 		// so, we always make sure the resultant height is an integer to make sure cutoff doesn't happen, and scale width from that
85 		// (you'll see this scattered throughout the function)
86 		// this is probably not the best way, so if you're familiar with this problem and have a better solution, please make a pull request!
87 
88 		if(mTargetIsMax)
89 		{
90 
91 			mSize = textureSize;
92 
93 			Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
94 
95 			if(resizeScale.x() < resizeScale.y())
96 			{
97 				mSize[0] *= resizeScale.x();
98 				mSize[1] *= resizeScale.x();
99 			}else{
100 				mSize[0] *= resizeScale.y();
101 				mSize[1] *= resizeScale.y();
102 			}
103 
104 			// for SVG rasterization, always calculate width from rounded height (see comment above)
105 			mSize[1] = Math::round(mSize[1]);
106 			mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x();
107 
108 		}else{
109 			// if both components are set, we just stretch
110 			// if no components are set, we don't resize at all
111 			mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
112 
113 			// if only one component is set, we resize in a way that maintains aspect ratio
114 			// for SVG rasterization, we always calculate width from rounded height (see comment above)
115 			if(!mTargetSize.x() && mTargetSize.y())
116 			{
117 				mSize[1] = Math::round(mTargetSize.y());
118 				mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
119 			}else if(mTargetSize.x() && !mTargetSize.y())
120 			{
121 				mSize[1] = Math::round((mTargetSize.x() / textureSize.x()) * textureSize.y());
122 				mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
123 			}
124 		}
125 
126 	// mSize.y() should already be rounded
127 	mTexture->rasterizeAt((size_t)Math::round(mSize.x()), (size_t)Math::round(mSize.y()));
128 
129 	onSizeChanged();
130 }
131 
render(const Transform4x4f & parentTrans)132 void VideoVlcComponent::render(const Transform4x4f& parentTrans)
133 {
134 	if (!isVisible())
135 		return;
136 
137 	VideoComponent::render(parentTrans);
138 	Transform4x4f trans = parentTrans * getTransform();
139 	GuiComponent::renderChildren(trans);
140 	Renderer::setMatrix(trans);
141 
142 	if (mIsPlaying && mContext.valid)
143 	{
144 		const unsigned int fadeIn = (unsigned int)(Math::clamp(0.0f, mFadeIn, 1.0f) * 255.0f);
145 		const unsigned int color  = Renderer::convertColor((fadeIn << 24) | (fadeIn << 16) | (fadeIn << 8) | 255);
146 		Renderer::Vertex   vertices[4];
147 
148 		vertices[0] = { { 0.0f     , 0.0f      }, { 0.0f, 0.0f }, color };
149 		vertices[1] = { { 0.0f     , mSize.y() }, { 0.0f, 1.0f }, color };
150 		vertices[2] = { { mSize.x(), 0.0f      }, { 1.0f, 0.0f }, color };
151 		vertices[3] = { { mSize.x(), mSize.y() }, { 1.0f, 1.0f }, color };
152 
153 		// round vertices
154 		for(int i = 0; i < 4; ++i)
155 			vertices[i].pos.round();
156 
157 		// Build a texture for the video frame
158 		mTexture->initFromPixels((unsigned char*)mContext.surface->pixels, mContext.surface->w, mContext.surface->h);
159 		mTexture->bind();
160 
161 		// Render it
162 		Renderer::drawTriangleStrips(&vertices[0], 4);
163 	}
164 	else
165 	{
166 		VideoComponent::renderSnapshot(parentTrans);
167 	}
168 }
169 
setupContext()170 void VideoVlcComponent::setupContext()
171 {
172 	if (!mContext.valid)
173 	{
174 		// Create an RGBA surface to render the video into
175 		mContext.surface = SDL_CreateRGBSurface(SDL_SWSURFACE, (int)mVideoWidth, (int)mVideoHeight, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
176 		mContext.mutex = SDL_CreateMutex();
177 		mContext.valid = true;
178 		resize();
179 	}
180 }
181 
freeContext()182 void VideoVlcComponent::freeContext()
183 {
184 	if (mContext.valid)
185 	{
186 		SDL_FreeSurface(mContext.surface);
187 		SDL_DestroyMutex(mContext.mutex);
188 		mContext.valid = false;
189 	}
190 }
191 
setupVLC(std::string subtitles)192 void VideoVlcComponent::setupVLC(std::string subtitles)
193 {
194 	// If VLC hasn't been initialised yet then do it now
195 	if (!mVLC)
196 	{
197 		const char** args;
198 		const char* newargs[] = { "--quiet", "--sub-file", subtitles.c_str() };
199 		const char* singleargs[] = { "--quiet" };
200 		int argslen = 0;
201 
202 		if (!subtitles.empty())
203 		{
204 			argslen = sizeof(newargs) / sizeof(newargs[0]);
205 			args = newargs;
206 		}
207 		else
208 		{
209 			argslen = sizeof(singleargs) / sizeof(singleargs[0]);
210 			args = singleargs;
211 		}
212 		mVLC = libvlc_new(argslen, args);
213 	}
214 }
215 
handleLooping()216 void VideoVlcComponent::handleLooping()
217 {
218 	if (mIsPlaying && mMediaPlayer)
219 	{
220 		libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
221 		if (state == libvlc_Ended)
222 		{
223 			if (!Settings::getInstance()->getBool("VideoAudio") ||
224 				(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
225 			{
226 				libvlc_audio_set_mute(mMediaPlayer, 1);
227 			}
228 			//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
229 			libvlc_media_player_set_media(mMediaPlayer, mMedia);
230 			libvlc_media_player_play(mMediaPlayer);
231 		}
232 	}
233 }
234 
startVideo()235 void VideoVlcComponent::startVideo()
236 {
237 	if (!mIsPlaying) {
238 		mVideoWidth = 0;
239 		mVideoHeight = 0;
240 
241 #ifdef WIN32
242 		std::string path(Utils::String::replace(mVideoPath, "/", "\\"));
243 #else
244 		std::string path(mVideoPath);
245 #endif
246 		// Make sure we have a video path
247 		if (mVLC && (path.size() > 0))
248 		{
249 			// Set the video that we are going to be playing so we don't attempt to restart it
250 			mPlayingVideoPath = mVideoPath;
251 
252 			// Open the media
253 			mMedia = libvlc_media_new_path(mVLC, path.c_str());
254 			if (mMedia)
255 			{
256 				unsigned track_count;
257 				// Get the media metadata so we can find the aspect ratio
258 				libvlc_media_parse(mMedia);
259 				libvlc_media_track_t** tracks;
260 				track_count = libvlc_media_tracks_get(mMedia, &tracks);
261 				for (unsigned track = 0; track < track_count; ++track)
262 				{
263 					if (tracks[track]->i_type == libvlc_track_video)
264 					{
265 						mVideoWidth = tracks[track]->video->i_width;
266 						mVideoHeight = tracks[track]->video->i_height;
267 						break;
268 					}
269 				}
270 				libvlc_media_tracks_release(tracks, track_count);
271 
272 				// Make sure we found a valid video track
273 				if ((mVideoWidth > 0) && (mVideoHeight > 0))
274 				{
275 #ifndef _RPI_
276 					if (mScreensaverMode)
277 					{
278 						if(!Settings::getInstance()->getBool("CaptionsCompatibility")) {
279 
280 							Vector2f resizeScale((Renderer::getScreenWidth() / (float)mVideoWidth), (Renderer::getScreenHeight() / (float)mVideoHeight));
281 
282 							if(resizeScale.x() < resizeScale.y())
283 							{
284 								mVideoWidth = (unsigned int) (mVideoWidth * resizeScale.x());
285 								mVideoHeight = (unsigned int) (mVideoHeight * resizeScale.x());
286 							}else{
287 								mVideoWidth = (unsigned int) (mVideoWidth * resizeScale.y());
288 								mVideoHeight = (unsigned int) (mVideoHeight * resizeScale.y());
289 							}
290 						}
291 					}
292 #endif
293 					PowerSaver::pause();
294 					setupContext();
295 
296 					// Setup the media player
297 					mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
298 
299 					if (!Settings::getInstance()->getBool("VideoAudio") ||
300 						(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
301 					{
302 						libvlc_audio_set_mute(mMediaPlayer, 1);
303 					}
304 
305 					libvlc_media_player_play(mMediaPlayer);
306 					libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display, (void*)&mContext);
307 					libvlc_video_set_format(mMediaPlayer, "RGBA", (int)mVideoWidth, (int)mVideoHeight, (int)mVideoWidth * 4);
308 
309 					// Update the playing state
310 					mIsPlaying = true;
311 					mFadeIn = 0.0f;
312 				}
313 			}
314 		}
315 	}
316 }
317 
stopVideo()318 void VideoVlcComponent::stopVideo()
319 {
320 	mIsPlaying = false;
321 	mStartDelayed = false;
322 	// Release the media player so it stops calling back to us
323 	if (mMediaPlayer)
324 	{
325 		libvlc_media_player_stop(mMediaPlayer);
326 		libvlc_media_player_release(mMediaPlayer);
327 		libvlc_media_release(mMedia);
328 		mMediaPlayer = NULL;
329 		freeContext();
330 		PowerSaver::resume();
331 	}
332 }
333