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