implement synchronization feature for player
Stefan Schuermans

Stefan Schuermans commited on 2014-01-03 21:44:12
Showing 17 changed files, with 309 additions and 25 deletions.

... ...
@@ -35,6 +35,25 @@
35 35
       <br>
36 36
       If this feature is not needed, the file <code>haltstream</code>
37 37
       should not exist.
38
+      <br>
39
+      This feature should not be used together with the synchronization
40
+      feature.
41
+    </p>
42
+    <h3>Input Synchronization Stream</h3>
43
+    <p>
44
+      The player can be synchronized to a master by using a synchronization
45
+      stream.
46
+      To achieve this, the name of the synchronization stream is written to
47
+      the file <code>insync</code>.
48
+      The player will then seek to the movie and the position requested via
49
+      the synchronization stream.
50
+      It will also go to pause mode whenever the synchronization stream
51
+      requests it.
52
+      <br>
53
+      If this feature is not needed, the file <code>insync</code>
54
+      should not exist.
55
+      <br>
56
+      This feature should not be used together with the halt stream feature.
38 57
     </p>
39 58
   </body>
40 59
 </html>
... ...
@@ -1,2 +1,3 @@
1 1
 *.d
2 2
 *.o
3
+.*.swp
... ...
@@ -36,15 +36,21 @@ Player::Player(const std::string &name, Mgrs &mgrs,
36 36
   Module(name, mgrs, dirBase),
37 37
   m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr),
38 38
   m_fileHaltStream(dirBase.getFile("haltstream"), mgrs.m_streamMgr),
39
+  m_fileInSync(dirBase.getFile("insync"), mgrs.m_syncMgr),
39 40
   m_playlistTracker(*this, dirBase.getSubdir("playlist")),
40 41
   m_curValid(false),
41 42
   m_curEntry(m_playlistTracker.m_list.begin()),
42 43
   m_curFrame(0),
44
+  m_curFrameStart(0),
45
+  m_curFrameEnd(0),
43 46
   m_curChange(false),
44 47
   m_halted(false)
45 48
 {
46
-  // load playlist
49
+  m_maxDeviation.fromMs(30);
50
+
51
+  // init and load playlist
47 52
   m_fileHaltStream.setStreamRecv(this);
53
+  m_fileInSync.setSyncRecv(this);
48 54
   m_playlistTracker.init();
49 55
   checkCurChanged();
50 56
 }
... ...
@@ -55,9 +61,10 @@ Player::~Player()
55 61
   // cancel time callback request
56 62
   m_mgrs.m_callMgr.cancelTimeCall(this);
57 63
 
58
-  // free all movies
64
+  // free all movies, detach from stream sources
59 65
   m_playlistTracker.clear();
60 66
   m_fileHaltStream.setStreamRecv(NULL);
67
+  m_fileInSync.setSyncRecv(NULL);
61 68
 }
62 69
 
63 70
 /// check for update of configuration
... ...
@@ -73,6 +80,10 @@ void Player::updateConfig()
73 80
   if (m_fileHaltStream.checkModified())
74 81
     m_fileHaltStream.update();
75 82
 
83
+  // input sync stream name file was modified -> re-get input sync stream
84
+  if (m_fileInSync.checkModified())
85
+    m_fileInSync.update();
86
+
76 87
   // playlist update
77 88
   m_playlistTracker.updateConfig();
78 89
   checkCurChanged();
... ...
@@ -88,31 +99,155 @@ void Player::setFrame(const std::string &stream, stBlinkenFrame *pFrame)
88 99
   // this is coming from the halt stream, which will halt the player
89 100
   // whenever a frame is available on this halt stream
90 101
 
91
-  // halt stream came to life -> halt player
92
-  if (pFrame && !m_halted) {
93
-    m_halted = true;
94
-    if (m_curValid) {
95
-      // store remaining frame time
96
-      m_remainTime = m_nextTime - Time::now();
102
+  // halt stream active -> halt player
103
+  if (pFrame)
104
+    halt();
105
+
106
+  // halt stream inactive -> continue playing
107
+  else
108
+    cont();
109
+
110
+  (void)stream; // unused
111
+}
112
+
113
+/**
114
+ * @brief send synchronization information
115
+ * @param[in] sync sync stream name
116
+ * @param[in] pInfo synchronization information
117
+ */
118
+void Player::sendInfo(const std::string &sync, Info &info)
119
+{
120
+  bool change = false, seekPos = true;
121
+
122
+  // halt player if pause flag is set
123
+  if (info.pause)
124
+    halt();
125
+
126
+  // seek to other movie if not correct one
127
+  if (m_curEntry != m_playlistTracker.m_list.end() &&
128
+      !checkName(m_curEntry->m_name, info.name)) {
129
+    // find movie by name
130
+    PlaylistIt it = m_playlistTracker.m_list.begin();
131
+    while (it != m_playlistTracker.m_list.end()) {
132
+      if (checkName(it->m_name, info.name))
133
+        break;
134
+      ++it;
135
+    }
136
+    // movie found -> seek to it
137
+    if (it != m_playlistTracker.m_list.end()) {
138
+      change = true;
139
+      m_curValid = false;
140
+      m_curEntry = it;
141
+      m_curFrame = 0;
142
+      m_remainTime = Time::zero;
143
+      if (m_curEntry->m_pObj->m_pMovie) {
144
+        stBlinkenFrame *pFrame =
145
+          BlinkenMovieGetFrame(m_curEntry->m_pObj->m_pMovie, m_curFrame);
146
+        if (pFrame) {
147
+          m_remainTime.fromMs(BlinkenFrameGetDuration(pFrame));
148
+          m_curValid = true;
149
+        }
150
+      }
151
+      m_curFrameStart = Time::zero;
152
+      m_curFrameEnd = m_remainTime;
153
+      m_nextTime = Time::now() + m_remainTime;
154
+    }
155
+    // movie not found -> do not seek to position
156
+    else {
157
+      seekPos = false;
158
+    }
159
+  }
160
+
161
+  Time now = Time::now();
162
+
163
+  if (m_curValid && seekPos) {
164
+
165
+    // get frame count of current movie
166
+    int frameCnt = BlinkenMovieGetFrameCnt(m_curEntry->m_pObj->m_pMovie);
167
+
168
+    // get remaining frame time and current position in movie
169
+    if (!m_halted) { // remaining frame time always up-to-date in halted mode
170
+      m_remainTime = m_nextTime - now;
97 171
       if (m_remainTime < Time::zero)
98 172
         m_remainTime = Time::zero;
99 173
     }
100
-    // cancel time call
101
-    m_mgrs.m_callMgr.cancelTimeCall(this);
174
+    Time pos = m_curFrameEnd - m_remainTime;
175
+
176
+    // seek to earlier position in movie if needed
177
+    while (pos > info.pos + m_maxDeviation) {
178
+      change = true;
179
+      Time frame = m_curFrameEnd - m_curFrameStart;
180
+      Time shown = frame - m_remainTime;
181
+      Time goback = pos - info.pos;
182
+      // correct by just showing current frame some time longer
183
+      if (goback <= shown) {
184
+        pos -= goback;
185
+        m_remainTime += goback;
186
+        break;
102 187
       }
188
+      // go back to begin of current frame
189
+      goback -= shown;
190
+      pos -= shown;
191
+      m_remainTime = frame;
192
+      // go back to end of previous frame
193
+      if (m_curFrame <= 0)
194
+        break;
195
+      m_curFrame--;
196
+      m_curFrameEnd = m_curFrameStart;
197
+      frame = Time::zero;
198
+      stBlinkenFrame *pFrame =
199
+        BlinkenMovieGetFrame(m_curEntry->m_pObj->m_pMovie, m_curFrame);
200
+      if (pFrame)
201
+        frame.fromMs(BlinkenFrameGetDuration(pFrame));
202
+      m_curFrameStart = m_curFrameEnd - frame;
203
+      m_remainTime = Time::zero;
204
+    } // while (pos > info.pos + m_maxDeviation)
103 205
 
104
-  // halt stream ended -> continue playing
105
-  else if (!pFrame && m_halted) {
106
-    m_halted = false;
107
-    if (m_curValid) {
108
-      // determine time for next frame
109
-      m_nextTime = Time::now() + m_remainTime;
110
-      // schedule time call
206
+    // seek to later position in movie if needed
207
+    while (pos + m_maxDeviation < info.pos) {
208
+      change = true;
209
+      Time frame = m_curFrameEnd - m_curFrameStart;
210
+      Time go = info.pos - pos;
211
+      // correct by just showing current frame some time shorter
212
+      if (go <= m_remainTime) {
213
+        pos += go;
214
+        m_remainTime -= go;
215
+        break;
216
+      }
217
+      // go to end of current frame
218
+      go -= m_remainTime;
219
+      pos += m_remainTime;
220
+      m_remainTime = Time::zero;
221
+      // go to begin of next frame
222
+      if (m_curFrame >= frameCnt)
223
+        break;
224
+      m_curFrame++;
225
+      m_curFrameStart = m_curFrameEnd;
226
+      frame = Time::zero;
227
+      stBlinkenFrame *pFrame =
228
+        BlinkenMovieGetFrame(m_curEntry->m_pObj->m_pMovie, m_curFrame);
229
+      if (pFrame)
230
+        frame.fromMs(BlinkenFrameGetDuration(pFrame));
231
+      m_curFrameEnd = m_curFrameStart + frame;
232
+      m_remainTime = frame;
233
+    } // while (pos + m_maxDeviation < info.pos)
234
+
235
+  } // if (m_curValid)
236
+
237
+  // send frame and update timed callback if something changed
238
+  if (change) {
239
+    sendFrame();
240
+    if (m_curValid && !m_halted) {
241
+      m_nextTime = now + m_remainTime; // remaining time changed -> update
111 242
       m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
112 243
     }
113 244
   }
114 245
 
115
-  (void)stream; // unused
246
+  // continue playing if pause flag is not set
247
+  if (!info.pause)
248
+    cont();
249
+
250
+  (void)sync; // unused
116 251
 }
117 252
 
118 253
 /// callback when requested time reached
... ...
@@ -122,7 +257,7 @@ void Player::timeCall()
122 257
   if (m_halted)
123 258
     return;
124 259
 
125
-  // leave if time is not yet ready to next frame
260
+  // leave if time is not yet ready for next frame
126 261
   if (Time::now() < m_nextTime) {
127 262
     // request call at time for next frame
128 263
     m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
... ...
@@ -131,11 +266,49 @@ void Player::timeCall()
131 266
 
132 267
   // go to next frame
133 268
   ++m_curFrame;
269
+  m_curFrameStart = m_curFrameEnd;
134 270
 
135 271
   // process new current frame
136 272
   procFrame();
137 273
 }
138 274
 
275
+/// halt player
276
+void Player::halt()
277
+{
278
+  // player already halted -> leave
279
+  if (m_halted)
280
+    return;
281
+
282
+  m_halted = true;
283
+
284
+  if (m_curValid) {
285
+    // store remaining frame time
286
+    m_remainTime = m_nextTime - Time::now();
287
+    if (m_remainTime < Time::zero)
288
+      m_remainTime = Time::zero;
289
+  }
290
+
291
+  // cancel time call
292
+  m_mgrs.m_callMgr.cancelTimeCall(this);
293
+}
294
+
295
+/// continue playing
296
+void Player::cont()
297
+{
298
+  // player not halted -> leave
299
+  if (!m_halted)
300
+    return;
301
+
302
+  m_halted = false;
303
+
304
+  if (m_curValid) {
305
+    // determine time for next frame
306
+    m_nextTime = Time::now() + m_remainTime;
307
+    // schedule time call
308
+    m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
309
+  }
310
+}
311
+
139 312
 /// check if current movie changed and react
140 313
 void Player::checkCurChanged()
141 314
 {
... ...
@@ -145,6 +318,8 @@ void Player::checkCurChanged()
145 318
 
146 319
     // go to begin of new current movie and start playing now
147 320
     m_curFrame = 0;
321
+    m_curFrameStart = 0;
322
+    m_curFrameEnd = 0;
148 323
     m_remainTime = Time::zero;
149 324
     m_nextTime = Time::now();
150 325
     procFrame();
... ...
@@ -164,6 +339,8 @@ void Player::procFrame()
164 339
     while (m_curEntry == m_playlistTracker.m_list.end()) {
165 340
       m_curEntry = m_playlistTracker.m_list.begin();
166 341
       m_curFrame = 0;
342
+      m_curFrameStart = 0;
343
+      m_curFrameEnd = 0;
167 344
       // detect empty playlist or playlist with only empty movies
168 345
       if (wrapped) {
169 346
         m_curValid = false;
... ...
@@ -180,7 +357,9 @@ void Player::procFrame()
180 357
     // movie finished -> next movie
181 358
     ++m_curEntry;
182 359
     m_curFrame = 0;
183
-  }
360
+    m_curFrame = 0;
361
+    m_curFrameStart = 0;
362
+  } // while (true)
184 363
 
185 364
   // send new frame to stream
186 365
   sendFrame();
... ...
@@ -192,6 +371,7 @@ void Player::procFrame()
192 371
       BlinkenMovieGetFrame(m_curEntry->m_pObj->m_pMovie, m_curFrame);
193 372
     m_remainTime.fromMs(BlinkenFrameGetDuration(pFrame));
194 373
     m_nextTime += m_remainTime;
374
+    m_curFrameEnd = m_curFrameStart + m_remainTime;
195 375
     // request call at time for next frame
196 376
     if (!m_halted)
197 377
       m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
... ...
@@ -14,18 +14,21 @@
14 14
 #include "Directory.h"
15 15
 #include "File.h"
16 16
 #include "InStreamFile.h"
17
+#include "InSyncFile.h"
17 18
 #include "ListTracker.h"
18 19
 #include "Mgrs.h"
19 20
 #include "Module.h"
20 21
 #include "OutStreamFile.h"
21 22
 #include "StreamRecv.h"
23
+#include "SyncRecv.h"
22 24
 #include "Time.h"
23 25
 #include "TimeCallee.h"
24 26
 
25 27
 namespace Blinker {
26 28
 
27 29
 /// a movie player
28
-class Player: public Module, public StreamRecv, public TimeCallee
30
+class Player: public Module, public StreamRecv, public SyncRecv,
31
+              public TimeCallee
29 32
 {
30 33
 protected:
31 34
   /// movie in playlist
... ...
@@ -67,10 +70,23 @@ public:
67 70
    */
68 71
   virtual void setFrame(const std::string &stream, stBlinkenFrame *pFrame);
69 72
 
73
+  /**
74
+   * @brief send synchronization information
75
+   * @param[in] sync sync stream name
76
+   * @param[in] pInfo synchronization information
77
+   */
78
+  virtual void sendInfo(const std::string &sync, Info &info);
79
+
70 80
   /// callback when requested time reached
71 81
   virtual void timeCall();
72 82
 
73 83
 protected:
84
+  /// halt player
85
+  void halt();
86
+
87
+  /// continue playing
88
+  void cont();
89
+
74 90
   /// check if current movie changed and react
75 91
   void checkCurChanged();
76 92
 
... ...
@@ -84,16 +100,22 @@ protected:
84 100
   OutStreamFile   m_fileOutStream;   ///< output stream name file
85 101
   InStreamFile    m_fileHaltStream;  /**< halt stream name file
86 102
                                           (player halts if stream active) */
103
+  InSyncFile      m_fileInSync;      ///< input sync stream
87 104
   PlaylistTracker m_playlistTracker; ///< current playlist
88 105
   bool            m_curValid;        ///< if there is a current frame
89 106
   PlaylistIt      m_curEntry;        ///< current playlist entry
90 107
   int             m_curFrame;        ///< current frame in movie
108
+  Time            m_curFrameStart;   /**< start time of current frame
109
+                                          relative to start of movie */
110
+  Time            m_curFrameEnd;     /**< end time of current frame
111
+                                          relative to start of movie */
91 112
   bool            m_curChange;       ///< current movie changed
92 113
   bool            m_halted;          ///< if player is halted
93 114
   Time            m_remainTime;      /**< remaining time of current frame
94 115
                                           (valid if m_curValid && m_halted) */
95 116
   Time            m_nextTime;        /**< when to show next frame
96 117
                                           (valid if m_curValid && !m_halted) */
118
+  Time            m_maxDeviation;    ///< max. deviation from sync stream
97 119
 }; // class Player
98 120
 
99 121
 } // namespace Blinker
... ...
@@ -57,7 +57,7 @@ void SyncPrinter::sendInfo(const std::string &sync, Info &info)
57 57
 {
58 58
   std::cout << "sync pause=" << (info.pause ? "true" : "false") <<
59 59
                " name=\"" << info.name << "\"" <<
60
-               " pos=" << info.pos_ms << "ms" <<
60
+               " pos=" << info.pos.toFloatSec() << "s" <<
61 61
                std::endl;
62 62
   (void)sync; // unused
63 63
 }
... ...
@@ -194,7 +194,7 @@ void SyncReceiver<ADDR, SOCK>::procInfo(const std::string &data)
194 194
     return;
195 195
   info.pause = (Net2Host32(packet.flags) & PoSyPause) != 0;
196 196
   info.name = packet.name;
197
-  info.pos_ms = Net2Host32(packet.pos_ms);
197
+  info.pos.fromMs(Net2Host32(packet.pos_ms));
198 198
 
199 199
   // pass on sync information
200 200
   m_fileOutSync.sendInfo(info);
... ...
@@ -7,6 +7,40 @@
7 7
 
8 8
 namespace Blinker {
9 9
 
10
+/**
11
+ * @brief check if playlist entry name matches sync name
12
+ * @param[in] playlist name in playlist
13
+ * @param[in] sync name from synchronization information
14
+ * @return true if name matches, false otherwise
15
+ */
16
+bool SyncRecv::checkName(const std::string &playlist, const std::string &sync)
17
+{
18
+  std::string::size_type pos;
19
+  std::string noext;
20
+
21
+  // full match
22
+  if (playlist == sync)
23
+    return true;
24
+
25
+  // match without file extension
26
+  pos = playlist.rfind('.');
27
+  noext = playlist.substr(0, pos);
28
+  if (noext == sync)
29
+    return true;
30
+
31
+  // match without number prefix "[0-9]*[._-]?"
32
+  for (pos = 0; pos < noext.length() && std::isdigit(noext.at(pos)); ++pos);
33
+  if (pos < noext.length() &&
34
+      (noext.at(pos) == '.' || noext.at(pos) == '_' || noext.at(pos) == '-'))
35
+    ++pos;
36
+  noext = noext.substr(pos);
37
+  if (noext == sync)
38
+    return true;
39
+
40
+  // no match
41
+  return false;
42
+}
43
+
10 44
 /// constructor
11 45
 SyncRecv::SyncRecv()
12 46
 {
... ...
@@ -6,9 +6,10 @@
6 6
 #ifndef BLINKER_SYNCRECV_H
7 7
 #define BLINKER_SYNCRECV_H
8 8
 
9
-#include <stdint.h>
10 9
 #include <string>
11 10
 
11
+#include "Time.h"
12
+
12 13
 namespace Blinker {
13 14
 
14 15
 /// video sync stream receiver interface
... ...
@@ -19,9 +20,18 @@ public:
19 20
   struct Info {
20 21
     bool        pause; ///< if pause mode is active
21 22
     std::string name;  ///< name of current piece
22
-    uint32_t    pos_ms; ///< current position within the piece in milliseconds
23
+    Time        pos;   ///< current position within the piece
23 24
   };
24 25
 
26
+public:
27
+  /**
28
+   * @brief check if playlist entry name matches sync name
29
+   * @param[in] playlist name in playlist
30
+   * @param[in] sync name from synchronization information
31
+   * @return true if name matches, false otherwise
32
+   */
33
+  static bool checkName(const std::string &playlist, const std::string &sync);
34
+
25 35
 public:
26 36
   /// constructor
27 37
   SyncRecv();
... ...
@@ -1,2 +1,3 @@
1 1
 *.d
2 2
 *.o
3
+.*.swp
... ...
@@ -170,6 +170,15 @@ time_t Time::toSec() const
170 170
     return m_sec;
171 171
 }
172 172
 
173
+/**
174
+ * @brief convert to floating point seconds
175
+ * @return time in seconds
176
+ */
177
+float Time::toFloatSec()
178
+{
179
+  return m_sec + m_ns * 1.0e-9f;
180
+}
181
+
173 182
 /**
174 183
  * @brief convert to struct timeval
175 184
  * @param[out] tv struct timeval
... ...
@@ -73,6 +73,12 @@ public:
73 73
    */
74 74
   time_t toSec() const;
75 75
 
76
+  /**
77
+   * @brief convert to floating point seconds
78
+   * @return time in seconds
79
+   */
80
+  float toFloatSec();
81
+
76 82
   /**
77 83
    * @brief convert to struct timeval
78 84
    * @param[out] tv struct timeval
79 85