1 /*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "SeekHandler.h"
10
11 #include "Application.h"
12 #include "FileItem.h"
13 #include "ServiceBroker.h"
14 #include "cores/DataCacheCore.h"
15 #include "guilib/GUIComponent.h"
16 #include "guilib/GUIWindowManager.h"
17 #include "guilib/LocalizeStrings.h"
18 #include "settings/AdvancedSettings.h"
19 #include "settings/Settings.h"
20 #include "settings/SettingsComponent.h"
21 #include "settings/lib/Setting.h"
22 #include "settings/lib/SettingDefinitions.h"
23 #include "utils/MathUtils.h"
24 #include "utils/StringUtils.h"
25 #include "utils/Variant.h"
26 #include "utils/log.h"
27 #include "windowing/GraphicContext.h"
28
29 #include <cmath>
30 #include <stdlib.h>
31
~CSeekHandler()32 CSeekHandler::~CSeekHandler()
33 {
34 m_seekDelays.clear();
35 m_forwardSeekSteps.clear();
36 m_backwardSeekSteps.clear();
37 }
38
Configure()39 void CSeekHandler::Configure()
40 {
41 Reset();
42
43 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
44
45 m_seekDelays.clear();
46 m_seekDelays.insert(std::make_pair(SEEK_TYPE_VIDEO, settings->GetInt(CSettings::SETTING_VIDEOPLAYER_SEEKDELAY)));
47 m_seekDelays.insert(std::make_pair(SEEK_TYPE_MUSIC, settings->GetInt(CSettings::SETTING_MUSICPLAYER_SEEKDELAY)));
48
49 m_forwardSeekSteps.clear();
50 m_backwardSeekSteps.clear();
51
52 std::map<SeekType, std::string> seekTypeSettingMap;
53 seekTypeSettingMap.insert(std::make_pair(SEEK_TYPE_VIDEO, CSettings::SETTING_VIDEOPLAYER_SEEKSTEPS));
54 seekTypeSettingMap.insert(std::make_pair(SEEK_TYPE_MUSIC, CSettings::SETTING_MUSICPLAYER_SEEKSTEPS));
55
56 for (std::map<SeekType, std::string>::iterator it = seekTypeSettingMap.begin(); it!=seekTypeSettingMap.end(); ++it)
57 {
58 std::vector<int> forwardSeekSteps;
59 std::vector<int> backwardSeekSteps;
60
61 std::vector<CVariant> seekSteps = settings->GetList(it->second);
62 for (std::vector<CVariant>::iterator it = seekSteps.begin(); it != seekSteps.end(); ++it)
63 {
64 int stepSeconds = static_cast<int>((*it).asInteger());
65 if (stepSeconds < 0)
66 backwardSeekSteps.insert(backwardSeekSteps.begin(), stepSeconds);
67 else
68 forwardSeekSteps.push_back(stepSeconds);
69 }
70
71 m_forwardSeekSteps.insert(std::make_pair(it->first, forwardSeekSteps));
72 m_backwardSeekSteps.insert(std::make_pair(it->first, backwardSeekSteps));
73 }
74 }
75
Reset()76 void CSeekHandler::Reset()
77 {
78 m_requireSeek = false;
79 m_analogSeek = false;
80 m_seekStep = 0;
81 m_seekSize = 0;
82 m_timeCodePosition = 0;
83 }
84
GetSeekStepSize(SeekType type,int step)85 int CSeekHandler::GetSeekStepSize(SeekType type, int step)
86 {
87 if (step == 0)
88 return 0;
89
90 std::vector<int> seekSteps(step > 0 ? m_forwardSeekSteps.at(type) : m_backwardSeekSteps.at(type));
91
92 if (seekSteps.empty())
93 {
94 CLog::Log(LOGERROR, "SeekHandler - %s - No %s %s seek steps configured.", __FUNCTION__,
95 (type == SeekType::SEEK_TYPE_VIDEO ? "video" : "music"), (step > 0 ? "forward" : "backward"));
96 return 0;
97 }
98
99 int seconds = 0;
100
101 // when exceeding the selected amount of steps repeat/sum up the last step size
102 if (static_cast<size_t>(abs(step)) <= seekSteps.size())
103 seconds = seekSteps.at(abs(step) - 1);
104 else
105 seconds = seekSteps.back() * (abs(step) - seekSteps.size() + 1);
106
107 return seconds;
108 }
109
Seek(bool forward,float amount,float duration,bool analogSeek,SeekType type)110 void CSeekHandler::Seek(bool forward, float amount, float duration /* = 0 */, bool analogSeek /* = false */, SeekType type /* = SEEK_TYPE_VIDEO */)
111 {
112 CSingleLock lock(m_critSection);
113
114 // not yet seeking
115 if (!m_requireSeek)
116 {
117 // use only the first step forward/backward for a seek without a delay
118 if (!analogSeek && m_seekDelays.at(type) == 0)
119 {
120 SeekSeconds(GetSeekStepSize(type, forward ? 1 : -1));
121 return;
122 }
123
124 m_requireSeek = true;
125 m_analogSeek = analogSeek;
126 m_seekDelay = analogSeek ? analogSeekDelay : m_seekDelays.at(type);
127 }
128
129 // calculate our seek amount
130 if (analogSeek)
131 {
132 //100% over 1 second.
133 float speed = 100.0f;
134 if( duration )
135 speed *= duration;
136 else
137 speed /= CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
138
139 double totalTime = g_application.GetTotalTime();
140 if (totalTime < 0)
141 totalTime = 0;
142
143 double seekSize = (amount * amount * speed) * totalTime / 100;
144 if (forward)
145 SetSeekSize(m_seekSize + seekSize);
146 else
147 SetSeekSize(m_seekSize - seekSize);
148 }
149 else
150 {
151 m_seekStep += forward ? 1 : -1;
152 int seekSeconds = GetSeekStepSize(type, m_seekStep);
153 if (seekSeconds != 0)
154 {
155 SetSeekSize(seekSeconds);
156 }
157 else
158 {
159 // nothing to do, abort seeking
160 Reset();
161 }
162 }
163 m_seekChanged = true;
164 m_timer.StartZero();
165 }
166
SeekSeconds(int seconds)167 void CSeekHandler::SeekSeconds(int seconds)
168 {
169 // abort if we do not have a play time or already perform a seek
170 if (seconds == 0)
171 return;
172
173 CSingleLock lock(m_critSection);
174 SetSeekSize(seconds);
175
176 // perform relative seek
177 g_application.GetAppPlayer().SeekTimeRelative(static_cast<int64_t>(seconds * 1000));
178
179 Reset();
180 }
181
GetSeekSize() const182 int CSeekHandler::GetSeekSize() const
183 {
184 return MathUtils::round_int(m_seekSize);
185 }
186
SetSeekSize(double seekSize)187 void CSeekHandler::SetSeekSize(double seekSize)
188 {
189 CApplicationPlayer& player = g_application.GetAppPlayer();
190 int64_t playTime = player.GetTime();
191 double minSeekSize = (player.GetMinTime() - playTime) / 1000.0;
192 double maxSeekSize = (player.GetMaxTime() - playTime) / 1000.0;
193
194 m_seekSize = seekSize > 0
195 ? std::min(seekSize, maxSeekSize)
196 : std::max(seekSize, minSeekSize);
197 }
198
InProgress() const199 bool CSeekHandler::InProgress() const
200 {
201 return m_requireSeek || CServiceBroker::GetDataCacheCore().IsSeeking();
202 }
203
FrameMove()204 void CSeekHandler::FrameMove()
205 {
206 if (m_timer.GetElapsedMilliseconds() >= m_seekDelay && m_requireSeek)
207 {
208 CSingleLock lock(m_critSection);
209
210 // perform relative seek
211 g_application.GetAppPlayer().SeekTimeRelative(static_cast<int64_t>(m_seekSize * 1000));
212
213 m_seekChanged = true;
214
215 Reset();
216 }
217
218 if (m_timeCodePosition > 0 && m_timerTimeCode.GetElapsedMilliseconds() >= 2500)
219 {
220 m_timeCodePosition = 0;
221 }
222
223 if (m_seekChanged)
224 {
225 m_seekChanged = false;
226 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_STATE_CHANGED);
227 }
228 }
229
SettingOptionsSeekStepsFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)230 void CSeekHandler::SettingOptionsSeekStepsFiller(const SettingConstPtr& setting,
231 std::vector<IntegerSettingOption>& list,
232 int& current,
233 void* data)
234 {
235 std::string label;
236 for (int seconds : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_seekSteps)
237 {
238 if (seconds > 60)
239 label = StringUtils::Format(g_localizeStrings.Get(14044).c_str(), seconds / 60);
240 else
241 label = StringUtils::Format(g_localizeStrings.Get(14045).c_str(), seconds);
242
243 list.insert(list.begin(), IntegerSettingOption("-" + label, seconds * -1));
244 list.emplace_back(label, seconds);
245 }
246 }
247
OnSettingChanged(const std::shared_ptr<const CSetting> & setting)248 void CSeekHandler::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
249 {
250 if (setting == NULL)
251 return;
252
253 if (setting->GetId() == CSettings::SETTING_VIDEOPLAYER_SEEKDELAY ||
254 setting->GetId() == CSettings::SETTING_VIDEOPLAYER_SEEKSTEPS ||
255 setting->GetId() == CSettings::SETTING_MUSICPLAYER_SEEKDELAY ||
256 setting->GetId() == CSettings::SETTING_MUSICPLAYER_SEEKSTEPS)
257 Configure();
258 }
259
OnAction(const CAction & action)260 bool CSeekHandler::OnAction(const CAction &action)
261 {
262 if (!g_application.GetAppPlayer().IsPlaying() || !g_application.GetAppPlayer().CanSeek())
263 return false;
264
265 SeekType type = g_application.CurrentFileItem().IsAudio() ? SEEK_TYPE_MUSIC : SEEK_TYPE_VIDEO;
266
267 if (SeekTimeCode(action))
268 return true;
269
270 switch (action.GetID())
271 {
272 case ACTION_SMALL_STEP_BACK:
273 case ACTION_STEP_BACK:
274 {
275 Seek(false, action.GetAmount(), action.GetRepeat(), false, type);
276 return true;
277 }
278 case ACTION_STEP_FORWARD:
279 {
280 Seek(true, action.GetAmount(), action.GetRepeat(), false, type);
281 return true;
282 }
283 case ACTION_BIG_STEP_BACK:
284 case ACTION_CHAPTER_OR_BIG_STEP_BACK:
285 {
286 g_application.GetAppPlayer().Seek(false, true, action.GetID() == ACTION_CHAPTER_OR_BIG_STEP_BACK);
287 return true;
288 }
289 case ACTION_BIG_STEP_FORWARD:
290 case ACTION_CHAPTER_OR_BIG_STEP_FORWARD:
291 {
292 g_application.GetAppPlayer().Seek(true, true, action.GetID() == ACTION_CHAPTER_OR_BIG_STEP_FORWARD);
293 return true;
294 }
295 case ACTION_NEXT_SCENE:
296 {
297 g_application.GetAppPlayer().SeekScene(true);
298 return true;
299 }
300 case ACTION_PREV_SCENE:
301 {
302 g_application.GetAppPlayer().SeekScene(false);
303 return true;
304 }
305 case ACTION_ANALOG_SEEK_FORWARD:
306 case ACTION_ANALOG_SEEK_BACK:
307 {
308 if (action.GetAmount())
309 Seek(action.GetID() == ACTION_ANALOG_SEEK_FORWARD, action.GetAmount(), action.GetRepeat(), true);
310 return true;
311 }
312 case REMOTE_0:
313 case REMOTE_1:
314 case REMOTE_2:
315 case REMOTE_3:
316 case REMOTE_4:
317 case REMOTE_5:
318 case REMOTE_6:
319 case REMOTE_7:
320 case REMOTE_8:
321 case REMOTE_9:
322 case ACTION_JUMP_SMS2:
323 case ACTION_JUMP_SMS3:
324 case ACTION_JUMP_SMS4:
325 case ACTION_JUMP_SMS5:
326 case ACTION_JUMP_SMS6:
327 case ACTION_JUMP_SMS7:
328 case ACTION_JUMP_SMS8:
329 case ACTION_JUMP_SMS9:
330 {
331 if (!g_application.CurrentFileItem().IsLiveTV())
332 {
333 ChangeTimeCode(action.GetID());
334 return true;
335 }
336 }
337 break;
338 default:
339 break;
340 }
341
342 return false;
343 }
344
SeekTimeCode(const CAction & action)345 bool CSeekHandler::SeekTimeCode(const CAction &action)
346 {
347 if (m_timeCodePosition <= 0)
348 return false;
349
350 switch (action.GetID())
351 {
352 case ACTION_SELECT_ITEM:
353 case ACTION_PLAYER_PLAY:
354 case ACTION_PAUSE:
355 {
356 CSingleLock lock(m_critSection);
357
358 g_application.SeekTime(GetTimeCodeSeconds());
359 Reset();
360 return true;
361 }
362 case ACTION_SMALL_STEP_BACK:
363 case ACTION_STEP_BACK:
364 case ACTION_BIG_STEP_BACK:
365 case ACTION_CHAPTER_OR_BIG_STEP_BACK:
366 case ACTION_MOVE_LEFT:
367 {
368 SeekSeconds(-GetTimeCodeSeconds());
369 return true;
370 }
371 case ACTION_STEP_FORWARD:
372 case ACTION_BIG_STEP_FORWARD:
373 case ACTION_CHAPTER_OR_BIG_STEP_FORWARD:
374 case ACTION_MOVE_RIGHT:
375 {
376 SeekSeconds(GetTimeCodeSeconds());
377 return true;
378 }
379 default:
380 break;
381 }
382 return false;
383 }
384
ChangeTimeCode(int remote)385 void CSeekHandler::ChangeTimeCode(int remote)
386 {
387 if (remote >= ACTION_JUMP_SMS2 && remote <= ACTION_JUMP_SMS9)
388 {
389 // cast to REMOTE_X
390 remote -= (ACTION_JUMP_SMS2 - REMOTE_2);
391 }
392 if (remote >= REMOTE_0 && remote <= REMOTE_9)
393 {
394 m_timerTimeCode.StartZero();
395
396 if (m_timeCodePosition < 6)
397 m_timeCodeStamp[m_timeCodePosition++] = remote - REMOTE_0;
398 else
399 {
400 // rotate around
401 for (int i = 0; i < 5; i++)
402 m_timeCodeStamp[i] = m_timeCodeStamp[i + 1];
403 m_timeCodeStamp[5] = remote - REMOTE_0;
404 }
405 }
406 }
407
GetTimeCodeSeconds() const408 int CSeekHandler::GetTimeCodeSeconds() const
409 {
410 if (m_timeCodePosition > 0)
411 {
412 // Convert the timestamp into an integer
413 int tot = 0;
414 for (int i = 0; i < m_timeCodePosition; i++)
415 tot = tot * 10 + m_timeCodeStamp[i];
416
417 // Interpret result as HHMMSS
418 int s = tot % 100; tot /= 100;
419 int m = tot % 100; tot /= 100;
420 int h = tot % 100;
421
422 return h * 3600 + m * 60 + s;
423 }
424 return 0;
425 }
426