vlcsync 0.2.1__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vlcsync/cli.py CHANGED
@@ -11,7 +11,8 @@ from vlcsync.syncer import AppConfig, Syncer
11
11
  from vlcsync.vlc_finder import print_exc
12
12
  from vlcsync.vlc_state import VlcId
13
13
 
14
- __version__ = "0.2.1"
14
+ # Also ref in project.toml
15
+ __version__ = "0.3.1"
15
16
 
16
17
 
17
18
  @click.command
@@ -28,20 +29,32 @@ __version__ = "0.2.1"
28
29
  required=False,
29
30
  is_flag=True,
30
31
  help="Disable discovery local vlc instances.")
31
- def main(rc_host_list: Set[VlcId], no_local_discover):
32
+ @click.option("--volume-sync",
33
+ "volume_sync",
34
+ default=False,
35
+ required=False,
36
+ is_flag=True,
37
+ help=
38
+ """
39
+ Enable volume sync between players. Useful for play video with external audio file.\n
40
+ I.e. first play video with disabled audio track ('vlc --no-audio video.mkv' option) and then play audio stream (`vlc audio.mka`).
41
+ \n
42
+ And you can control volume from main video player.
43
+ """)
44
+ def main(rc_host_list: Set[VlcId], no_local_discover, volume_sync):
32
45
  """Utility for synchronize multiple instances of VLC. Supports seek, play and pause."""
33
- print("Vlcsync started...")
46
+ print("Vlcsync started...", flush=True)
34
47
 
35
- app_config = AppConfig(rc_host_list, no_local_discover)
48
+ app_config = AppConfig(rc_host_list, no_local_discover, volume_sync)
36
49
  time.sleep(2) # Wait instances
37
50
  while True:
38
51
  try:
39
52
  with Syncer(app_config) as s:
40
53
  while True:
41
- s.do_sync()
54
+ s.do_check_synchronized()
42
55
  time.sleep(0.05)
43
56
  except KeyboardInterrupt:
44
57
  sys.exit(0)
45
58
  except Exception:
46
59
  print_exc()
47
- print("Exception detected. Restart sync...")
60
+ print("Exception detected. Restart sync...", flush=True)
vlcsync/syncer.py CHANGED
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  import sys
5
5
  import time
6
- from typing import Set
6
+ from typing import Set, List
7
7
 
8
8
  from loguru import logger
9
9
 
10
- from vlcsync.vlc import VLC_IFACE_IP, VlcProcs
10
+ from vlcsync.vlc import VLC_IFACE_IP, VlcProcs, Vlc
11
11
  from vlcsync.vlc_finder import LocalProcessFinderProvider, ExtraHostFinder
12
12
  from vlcsync.vlc_socket import VlcConnectionError
13
13
 
@@ -18,6 +18,7 @@ from vlcsync.vlc_state import VlcId
18
18
  class AppConfig:
19
19
  extra_rc_hosts: Set[VlcId]
20
20
  no_local_discovery: bool
21
+ volume_sync: bool
21
22
 
22
23
 
23
24
  class Syncer:
@@ -29,48 +30,77 @@ class Syncer:
29
30
  vlc_finders = set()
30
31
  if not self.app_config.no_local_discovery:
31
32
  vlc_finders.add(LocalProcessFinderProvider(VLC_IFACE_IP))
32
- print(f" Discover instances on {VLC_IFACE_IP} iface...")
33
+ print(f" Discover instances on {VLC_IFACE_IP} iface...", flush=True)
33
34
  else:
34
- print(" Local discovery vlc instances DISABLED...")
35
+ print(" Local discovery vlc instances DISABLED...", flush=True)
35
36
 
36
37
  if app_config.extra_rc_hosts:
37
38
  vlc_finders.add(ExtraHostFinder(app_config.extra_rc_hosts))
38
39
  for rc_host in app_config.extra_rc_hosts:
39
40
  rc_host: VlcId
40
- print(f" Manual host defined {rc_host.addr}:{rc_host.port}")
41
+ print(f" Manual host defined {rc_host.addr}:{rc_host.port}", flush=True)
41
42
  else:
42
- print(""" Manual vlc addresses ("--rc-host" args) NOT provided...""")
43
+ print(""" Manual vlc addresses ("--rc-host" args) NOT provided...""", flush=True)
43
44
 
44
45
  if not vlc_finders:
45
46
  print("\nTarget vlc instances not selected (nor autodiscover, nor manually). \n"
46
- """See: "vlcsync --help" for more info""")
47
+ """See: "vlcsync --help" for more info""", flush=True)
47
48
  sys.exit(1)
48
49
 
49
50
  self.env = VlcProcs(vlc_finders)
50
51
 
51
52
  def __enter__(self):
52
- self.do_sync()
53
+ self.do_check_synchronized()
53
54
  return self
54
55
 
55
56
  def __exit__(self, exc_type, exc_val, exc_tb):
56
57
  self.close()
57
58
 
58
- def do_sync(self):
59
- self.log_with_debounce("Sync...")
59
+ def do_check_synchronized(self):
60
+ self.log_with_debounce("do_check_synchronized()...")
60
61
  try:
61
- for vlc_id, vlc in self.env.all_vlc.items():
62
- is_changed, state = vlc.is_state_change()
63
- if not state.is_active():
64
- continue
62
+ if self.app_config.volume_sync:
63
+ self.sync_volume()
65
64
 
66
- if is_changed:
67
- print(f"\nVlc state change detected from ({vlc_id})")
68
- self.env.sync_all(state, vlc)
69
- return
65
+ self.sync_playstate()
70
66
 
71
67
  except VlcConnectionError as e:
72
68
  self.env.dereg(e.vlc_id)
73
69
 
70
+ def sync_playstate(self):
71
+ for vlc_id, vlc in self.env.all_vlc.items():
72
+ is_changed, state, playlist_changed = vlc.is_state_change()
73
+
74
+ if is_changed:
75
+ # Workaround Save volumes.
76
+ # When playlist items changed ALSO happen volumes sync by some reason. But SHOULD NOT!
77
+ volumes: List[tuple[Vlc, int]] = []
78
+ if not self.app_config.volume_sync and playlist_changed:
79
+ volumes = [(vlc1, vlc1.volume()) for _, vlc1 in self.env.all_vlc.items()]
80
+ #
81
+
82
+ print(f"\nVlc state change detected from ({vlc_id})", flush=True)
83
+ self.env.sync_all(state, vlc)
84
+
85
+ # Restore volumes if needed
86
+ if volumes:
87
+ for vlc_next, volume in volumes:
88
+ vlc_next.set_volume(volume)
89
+ # Trying to fix endless resyncing hang
90
+ # Cannot find reproduce steps for that
91
+ time.sleep(0.5)
92
+ break
93
+
94
+ def sync_volume(self):
95
+ for vlc_id, vlc in self.env.all_vlc.items():
96
+ cur_volume = vlc.volume()
97
+ if vlc.prev_volume != cur_volume:
98
+ for vlc_id_for_sync, vlc_for_sync in self.env.all_vlc.items():
99
+ vlc_for_sync.prev_volume = cur_volume
100
+ if vlc_for_sync != vlc:
101
+ vlc_for_sync.set_volume(cur_volume)
102
+ break
103
+
74
104
  def log_with_debounce(self, msg: str, _debounce=5):
75
105
  if time.time() > self.supress_log_until:
76
106
  logger.debug(msg)
vlcsync/vlc.py CHANGED
@@ -1,17 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from functools import lru_cache
4
+ import re
3
5
  import socket
4
6
  import threading
5
7
  import time
6
- from typing import Set
8
+ from typing import Set, List, Optional
7
9
 
8
10
  from loguru import logger
9
11
 
10
12
  from vlcsync.vlc_finder import IVlcListFinder
11
13
  from vlcsync.vlc_socket import VlcSocket
12
- from vlcsync.vlc_state import PlayState, State, VlcId
14
+ from vlcsync.vlc_state import PlayState, State, VlcId, PlayList, PlayListItem
13
15
 
14
16
  VLC_IFACE_IP = "127.0.0.42"
17
+ RE_PLAYSTATE_COMPILED = re.compile(r"\( state (playing|stopped|paused) \)")
18
+ RE_PLAYLIST_ITEM = re.compile(r'\| {2}([ *])(\d+) - ')
15
19
 
16
20
  socket.setdefaulttimeout(0.5)
17
21
 
@@ -21,67 +25,139 @@ class Vlc:
21
25
  self.vlc_id = vlc_id
22
26
  self.vlc_conn = VlcSocket(vlc_id)
23
27
  self.prev_state: State = self.cur_state()
28
+ self.prev_volume = self.volume()
24
29
 
25
30
  def play_state(self) -> PlayState:
26
31
  status = self.vlc_conn.cmd("status")
27
32
  return self._extract_state(status)
28
33
 
29
- def get_time(self) -> int | None:
34
+ def get_seek(self) -> int | None:
30
35
  seek = self.vlc_conn.cmd("get_time")
31
36
  if seek != '':
32
37
  return int(seek)
33
38
 
39
+ def playlist_goto(self, vlc_internal_index: int):
40
+ self.vlc_conn.cmd(f"goto {vlc_internal_index}")
41
+
42
+ def playlist(self) -> PlayList:
43
+ cmd_resp = self.vlc_conn.cmd("playlist")
44
+ return self._extract_playlist(cmd_resp)
45
+
46
+ def volume(self) -> Optional[int]:
47
+ vol = self.vlc_conn.cmd("volume")
48
+ if vol.strip() != '':
49
+ return int(float(vol.replace(",",'.')))
50
+ else:
51
+ return None
52
+
53
+ def set_volume(self, volume: int):
54
+ self.vlc_conn.cmd(f"volume {volume}")
55
+ self.prev_volume = volume
56
+
34
57
  def seek(self, seek: int):
35
58
  self.vlc_conn.cmd(f"seek {seek}")
36
59
 
60
+ def stop(self):
61
+ self.vlc_conn.cmd("stop")
62
+
63
+ def pause(self):
64
+ self.vlc_conn.cmd("pause")
65
+
66
+ def play(self):
67
+ self.vlc_conn.cmd("play")
68
+
37
69
  def cur_state(self) -> State:
38
- get_time = self.get_time()
70
+ cur_seek = self.get_seek()
39
71
 
40
72
  return State(self.play_state(),
41
- get_time,
42
- time.time() - (get_time or 0))
73
+ cur_seek,
74
+ self.playlist().active_order_index(),
75
+ # Abs time of video start
76
+ time.time() - (cur_seek or 0)
77
+ )
43
78
 
44
79
  def is_state_change(self) -> (bool, State):
45
80
  cur_state: State = self.cur_state()
46
81
  prev_state: State = self.prev_state
47
- is_change = not cur_state.same(prev_state)
82
+ full_same, playlist_same = cur_state.same(prev_state)
48
83
 
49
84
  # Return cur_state for reduce further socket communications
50
- return is_change, cur_state
85
+ return not full_same, cur_state, not playlist_same
51
86
 
52
87
  def sync_to(self, new_state: State, source: Vlc):
53
- if not new_state.is_active():
54
- return
55
-
56
- cur_play_state = self.play_state()
57
88
 
58
- if cur_play_state != new_state.play_state:
59
- self.play_if_pause(new_state, cur_play_state)
60
- self.pause_if_play(new_state, cur_play_state)
61
-
62
- # Sync all secondary, but main only when playing
63
- if source != self or cur_play_state == PlayState.PLAYING:
64
- self.seek(new_state.seek)
89
+ self._sync_playlist(new_state)
90
+ self._sync_playstate(new_state)
91
+ self._sync_timeline(new_state, source)
65
92
 
66
93
  self.prev_state = self.cur_state()
67
94
 
68
- def pause_if_play(self, new_state, cur_play_state):
69
- if cur_play_state == PlayState.PLAYING and new_state.play_state == PlayState.PAUSED:
70
- self.vlc_conn.cmd("pause")
95
+ def _sync_timeline(self, new_state: State, source: Vlc):
96
+ cur_play_state = self.play_state()
97
+
98
+ if cur_play_state == PlayState.PAUSED and source == self:
99
+ """
100
+ Skip sync seek with himself on pause (avoid flickering)
101
+ As half-seconds not supported and cannot to set.
102
+ """
103
+ pass
104
+ else:
105
+ # In all other cases
106
+ self.seek(new_state.seek)
71
107
 
72
- def play_if_pause(self, new_state, cur_play_state):
73
- if cur_play_state == PlayState.PAUSED and new_state.play_state == PlayState.PLAYING:
74
- self.vlc_conn.cmd("play")
108
+ def _sync_playstate(self, new_state: State):
109
+ cur_play_state: PlayState = self.play_state()
110
+ if cur_play_state != new_state.play_state:
111
+ if new_state.play_state == PlayState.STOPPED:
112
+ self.stop()
113
+ elif new_state.play_state == PlayState.PLAYING:
114
+ if cur_play_state in [PlayState.PAUSED, PlayState.STOPPED]:
115
+ self.play()
116
+ elif new_state.play_state == PlayState.PAUSED:
117
+ if cur_play_state == PlayState.PLAYING:
118
+ self.pause()
119
+ else:
120
+ logger.warning(f"Unknown new play state {new_state.play_state} for player")
121
+
122
+ def _sync_playlist(self, new_state: State):
123
+ cur_playlist = self.playlist()
124
+ if new_state.is_play_or_pause() and cur_playlist.active_order_index() != new_state.playlist_order_idx:
125
+ if new_state.playlist_order_idx is not None and len(cur_playlist.items) > new_state.playlist_order_idx:
126
+ self.playlist_goto(cur_playlist.items[new_state.playlist_order_idx].vlc_internal_index)
127
+ else:
128
+ self.stop()
75
129
 
76
130
  @staticmethod
77
- def _extract_state(status, _valid_states=(PlayState.PLAYING.value,
78
- PlayState.PAUSED.value,
79
- PlayState.STOPPED.value)):
80
- for pb_state in _valid_states:
81
- if pb_state in status:
82
- return PlayState(pb_state)
131
+ @lru_cache(maxsize=128)
132
+ def _extract_state(status: str):
133
+ match = RE_PLAYSTATE_COMPILED.search(status)
134
+ return PlayState(match.group(1)) if match else PlayState.UNKNOWN
83
135
 
84
- return PlayState.UNKNOWN
136
+ @staticmethod
137
+ @lru_cache(maxsize=128)
138
+ def _extract_playlist(resp: str) -> PlayList:
139
+ """ Playlist answer Format:
140
+ +----[ Playlist - playlist ]
141
+ | 1 - Плейлист
142
+ | 6 - Video 1.mkv (00:23:37) [played 1 time]
143
+ | *4 - Video 2.mkv (00:23:44) [played 2 times]
144
+ | 3 - Video 3.mkv (00:23:43) [played 1 time]
145
+ | 5 - Video 4.mkv (00:23:44) [played 1 time]
146
+ | 2 - Медиатека
147
+ | 12 - Video 6.mkv (00:23:44)
148
+ +----[ End of playlist ]
149
+ """
150
+
151
+ items: List[PlayListItem] = []
152
+ active: Optional[PlayListItem] = None
153
+
154
+ for idx, match in enumerate(re.finditer(RE_PLAYLIST_ITEM, resp)):
155
+ item = PlayListItem(idx, match.group(2))
156
+ items.append(item)
157
+ if match.group(1) == "*":
158
+ active = item
159
+
160
+ return PlayList(items, active)
85
161
 
86
162
  def __repr__(self):
87
163
  return f"Vlc({self.vlc_id}, {self.prev_state=})"
@@ -119,7 +195,7 @@ class VlcProcs:
119
195
  for vlc_id in vlc_candidates:
120
196
  if vlc_id not in self._vlc_instances.keys():
121
197
  if vlc := self.try_connect(vlc_id):
122
- print(f"Found active instance {vlc_id}, with state {vlc.cur_state()}")
198
+ print(f"Found active instance {vlc_id}, with state {vlc.cur_state()}", flush=True)
123
199
  self._vlc_instances[vlc_id] = vlc
124
200
 
125
201
  logger.debug(f"Compute all_vlc (took {time.time() - start:.3f})...")
@@ -131,35 +207,31 @@ class VlcProcs:
131
207
  return Vlc(vlc_id)
132
208
  except Exception as e:
133
209
  logger.opt(exception=True).debug("Cannot connect to {0}, cause: {1}", vlc_id, e)
134
- print(f"Cannot connect to {vlc_id} socket, cause: {e}. Skipping. Enable debug for more info. See --help. ")
210
+ print(f"Cannot connect to {vlc_id} socket, cause: {e}. Skipping. Enable debug for more info. See --help. ", flush=True)
135
211
  return None
136
212
 
137
213
  @property
138
214
  def all_vlc(self) -> dict[VlcId, Vlc]:
139
215
  return self._vlc_instances.copy() # copy: for thread safe
140
216
 
141
- def sync_all(self, state: State, source: Vlc):
217
+ def sync_all(self, state: State, source_vlc: Vlc):
142
218
  logger.debug(">" * 60)
143
- logger.debug(f"Detect change to {state} from {source.vlc_id}")
144
- logger.debug(f" old --> {source.prev_state} ")
219
+ logger.debug(f"Detect change to {state} from {source_vlc.vlc_id}")
220
+ logger.debug(f" old --> {source_vlc.prev_state} ")
145
221
  logger.debug(f" new --> {state} ")
146
- logger.debug(f" Time diff abs(old - new) {abs(state.time_diff - source.prev_state.time_diff)}")
222
+ logger.debug(f" Time diff abs(old - new) {abs(source_vlc.prev_state.vid_start_at - state.vid_start_at)}")
147
223
  logger.debug("<" * 60)
148
224
  logger.debug("")
149
- print(">>> Sync windows...")
150
- if not state.is_active():
151
- print(" Source window stopped. Skip sync")
152
- return
225
+ print(">>> Sync players...", flush=True)
153
226
 
154
227
  for next_pid, next_vlc in self.all_vlc.items():
155
228
  next_vlc: Vlc
156
- if next_vlc.cur_state().is_active():
157
- print(f" Sync {next_pid} to {state}")
158
- next_vlc.sync_to(state, source)
229
+ print(f" Sync {next_pid} to {state}", flush=True)
230
+ next_vlc.sync_to(state, source_vlc)
159
231
  print()
160
232
 
161
233
  def dereg(self, vlc_id: VlcId):
162
- print(f"Detect vlc instance closed {vlc_id}")
234
+ print(f"Detect vlc instance closed {vlc_id}", flush=True)
163
235
  if vlc_to_close := self._vlc_instances.pop(vlc_id, None):
164
236
  vlc_to_close.close()
165
237
 
@@ -172,5 +244,3 @@ class VlcProcs:
172
244
 
173
245
  def __del__(self):
174
246
  self.close()
175
-
176
-
vlcsync/vlc_finder.py CHANGED
@@ -28,16 +28,16 @@ class IVlcListFinder:
28
28
 
29
29
 
30
30
  class LocalProcessFinderProvider(IVlcListFinder):
31
- def __init__(self, iface):
32
- self.iface = iface
31
+ def __init__(self, iface: str):
32
+ self._iface = iface
33
33
 
34
34
  def get_vlc_list(self) -> Set[VlcId]:
35
35
  vlc_ports = set()
36
36
 
37
37
  for proc in self._find_vlc_procs():
38
- port = self._has_listen_port(proc, self.iface)
38
+ port = self._has_listen_port(proc, self._iface)
39
39
  if port:
40
- vlc_ports.add(VlcId(self.iface, port, proc.pid))
40
+ vlc_ports.add(VlcId(self._iface, port, proc.pid))
41
41
 
42
42
  return vlc_ports
43
43
 
@@ -90,11 +90,12 @@ class LocalProcessFinderProvider(IVlcListFinder):
90
90
 
91
91
  def print_exc():
92
92
  print("-" * 60)
93
- print("Exception in user code: ")
93
+ print("Exception in user code: ", flush=True)
94
94
  print("-" * 60)
95
95
  traceback.print_exc(file=sys.stdout)
96
96
  print("-" * 60)
97
97
  print()
98
+ sys.stdout.flush()
98
99
 
99
100
 
100
101
  class ExtraHostFinder(IVlcListFinder):
vlcsync/vlc_state.py CHANGED
@@ -38,16 +38,23 @@ class PlayState(Enum):
38
38
  class State:
39
39
  play_state: PlayState
40
40
  seek: int
41
- time_diff: float = field(repr=False)
41
+ playlist_order_idx: int
42
+ """
43
+ vid_start_at - is abs time of video start in given vlc
44
+
45
+ If this time changed it indicates time seek
46
+
47
+ See for details:
48
+ - play_in_same_pos()
49
+ - Vlc.cur_state()
50
+ """
51
+ vid_start_at: float = field(repr=False)
42
52
 
43
53
  def same(self, other: State):
44
- return (self.same_play_state(other) and
45
- (
46
- self.play_in_same_pos(other) or
47
- self.pause_is_same_pos(other) or
48
- self.both_stopped(other)
49
- )
50
- )
54
+ playlist_same = self.same_playlist_item(other)
55
+ full_same = (self.same_play_state(other) and playlist_same and (
56
+ self.play_in_same_pos(other) or self.pause_is_same_pos(other) or self.both_stopped(other)))
57
+ return full_same, playlist_same
51
58
 
52
59
  def same_play_state(self, other: State):
53
60
  return self.play_state == other.play_state
@@ -57,23 +64,41 @@ class State:
57
64
 
58
65
  def play_in_same_pos(self: State, other: State):
59
66
  """ Check time_diff only when play """
60
- desync_secs = abs(self.time_diff - other.time_diff)
67
+ desync_secs = abs(self.vid_start_at - other.vid_start_at)
61
68
 
62
69
  if 2 < desync_secs < MAX_DESYNC_SECONDS:
63
70
  logger.debug(f"Asynchronous anomaly between probes: {desync_secs} secs")
64
71
 
65
72
  return (
66
73
  self.play_state == other.play_state == PlayState.PLAYING and
67
- self.time_diff and other.time_diff and
74
+ self.vid_start_at and other.vid_start_at and
68
75
  desync_secs < MAX_DESYNC_SECONDS
69
76
  )
70
77
 
71
78
  def both_stopped(self, other: State):
72
79
  return self.play_state == other.play_state == PlayState.STOPPED
73
80
 
74
- def is_active(self):
81
+ def is_play_or_pause(self):
75
82
  return self.play_state in [PlayState.PLAYING, PlayState.PAUSED]
76
83
 
84
+ def same_playlist_item(self, other):
85
+ return self.playlist_order_idx == other.playlist_order_idx
86
+
87
+
88
+ @dataclass
89
+ class PlayListItem:
90
+ order_index: int
91
+ vlc_internal_index: int
92
+
93
+
94
+ @dataclass
95
+ class PlayList:
96
+ items: List[PlayListItem]
97
+ active_item: Optional[PlayListItem]
98
+
99
+ def active_order_index(self) -> Optional[int]:
100
+ return self.active_item.order_index if self.active_item else None
101
+
77
102
 
78
103
  @dataclass(frozen=True)
79
104
  class VlcId:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vlcsync
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: Utility for synchronize multiple instances of VLC. Supports seek, play and pause.
5
5
  Home-page: https://github.com/mrkeuz/vlcsync/
6
6
  License: MIT
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
29
29
  VLC Sync
30
30
  ========
31
31
 
32
- Utility for synchronize multiple instances of VLC. Supports seek, play and pause.
32
+ Utility for synchronize multiple instances of VLC. Supports seek, play and pause/stop, playlist and volume sync.
33
33
 
34
34
 
35
35
  #### Motivation
@@ -71,6 +71,10 @@ $ vlcsync --rc-host 192.168.1.100:12345 --rc-host 192.168.1.50:54321
71
71
  # For disable local discovery (only remote instances)
72
72
  $ vlcsync --no-local-discovery --rc-host 192.168.1.100:12345
73
73
 
74
+ # Started from version 0.3.0 (playlists sync)
75
+ # Support volume sync for exotic cases
76
+ $ vlcsync --volume-sync
77
+
74
78
  # For help and see all options
75
79
  $ vlcsync --help
76
80
  ```
@@ -89,7 +93,7 @@ Awesome [use-case](./docs/awesome.md) ideas
89
93
  Difference between videos can be **up to ~0.5 seconds** in worst case. Especially when playing from network share,
90
94
  due buffering time and network latency.
91
95
 
92
- - Currently, tested only on:
96
+ - Currently, tested on:
93
97
  - Linux (Ubuntu 20.04)
94
98
  - Windows 7 (32-bit)
95
99
  - Windows 10 (64-bit)
@@ -102,7 +106,7 @@ Awesome [use-case](./docs/awesome.md) ideas
102
106
  - [Syncplay](https://github.com/Syncplay/syncplay) - very promised, but little [complicated](https://github.com/Syncplay/syncplay/discussions/463) for sync different videos
103
107
  - [bino](https://bino3d.org/) - working, very strange controls, file dialog not working and only fullscreen
104
108
  - [gridplayer](https://github.com/vzhd1701/gridplayer) - low fps by some reason
105
- - [mpv](https://github.com/mpv-player/mpv) - with [mixing multiple videos](https://superuser.com/a/1325668/1272472) in one window. Unfortunally does not support multiple screens
109
+ - [mpv](https://github.com/mpv-player/mpv) - with [mixing multiple videos](https://superuser.com/a/1325668/1272472) in one window. Unfortunately does not support multiple screens
106
110
  - [AVPlayer](http://www.awesomevideoplayer.com/) - only Win, macOS, up to 4 videos in free version
107
111
 
108
112
  ## Contributing
@@ -0,0 +1,14 @@
1
+ vlcsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ vlcsync/cli.py,sha256=ssoMtW_DorOs0QK5-mEqm6itQQQVci86B8OA2Ed4Nw0,2005
3
+ vlcsync/cli_utils.py,sha256=hXSljymGPU5AzFr706Ea-81wxkVCYLhZY-ug-JExZ0E,883
4
+ vlcsync/main.py,sha256=An0NQ4jy3mfygwxko_UA3Sdwri9yidcUTTCP3SoyrpI,303
5
+ vlcsync/syncer.py,sha256=x9w0LWmCUtFlMFTi01q8BBos0d9fwUpOQMo0dko6n_0,3854
6
+ vlcsync/vlc.py,sha256=dBrg2TYQswhewtUveBilp0jadfMQAMRZkqgBukUK3F0,8528
7
+ vlcsync/vlc_finder.py,sha256=7BXhOeJG8lRqYJ7dbrLU3Ps8ghUPh666YkAZjNom-cw,2770
8
+ vlcsync/vlc_socket.py,sha256=WQAtNoitc49sst0oRG0bEuTkgw6a2TJXVE9iSWQaMa0,1850
9
+ vlcsync/vlc_state.py,sha256=8RpmxVS_zijqYw9rxpmnN5QwM3Sk1FmqgTxgCH3U8kU,3133
10
+ vlcsync-0.3.1.dist-info/entry_points.txt,sha256=2m_39oATnFzf5kuvlhkayyWSmW0tUefyNk9dVP4A7B0,45
11
+ vlcsync-0.3.1.dist-info/LICENSE.md,sha256=Q6ik40IpumjnuWfS1c69jnEyxAPTBV2v04DesGSgLPM,1074
12
+ vlcsync-0.3.1.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
13
+ vlcsync-0.3.1.dist-info/METADATA,sha256=UWdLvYjpLAGk9KC_0pGnDrpF9dEdwZM6qs9cmrj-qrs,4258
14
+ vlcsync-0.3.1.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- vlcsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- vlcsync/cli.py,sha256=y-NXVFVEc92PYSz6S1qZRn8t_RYtH8SK7FxEd6bAsuU,1382
3
- vlcsync/cli_utils.py,sha256=hXSljymGPU5AzFr706Ea-81wxkVCYLhZY-ug-JExZ0E,883
4
- vlcsync/main.py,sha256=An0NQ4jy3mfygwxko_UA3Sdwri9yidcUTTCP3SoyrpI,303
5
- vlcsync/syncer.py,sha256=_x5Lm3iOALgqukEyXi1R8-CJrGHp6vGFm8pKRX8YKwU,2503
6
- vlcsync/vlc.py,sha256=InpH8u9f2dG6jw-LerrJSygMvboyk84hksO7-QBh5Iw,5856
7
- vlcsync/vlc_finder.py,sha256=8cc1IktUqmaY8xjWjgO6uVkpvtb7tAwPOlPBck1-oZY,2727
8
- vlcsync/vlc_socket.py,sha256=WQAtNoitc49sst0oRG0bEuTkgw6a2TJXVE9iSWQaMa0,1850
9
- vlcsync/vlc_state.py,sha256=Y2jtVO9vHIJCqOdPzBAM9TC3e4R_wxhgYQsYwOgbi7U,2442
10
- vlcsync-0.2.1.dist-info/entry_points.txt,sha256=2m_39oATnFzf5kuvlhkayyWSmW0tUefyNk9dVP4A7B0,45
11
- vlcsync-0.2.1.dist-info/LICENSE.md,sha256=Q6ik40IpumjnuWfS1c69jnEyxAPTBV2v04DesGSgLPM,1074
12
- vlcsync-0.2.1.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
13
- vlcsync-0.2.1.dist-info/METADATA,sha256=TCCRF2Fynjgiolg_sNFi9ItcFX3fN0ca7omY5Dgw-X4,4121
14
- vlcsync-0.2.1.dist-info/RECORD,,