diff --git a/Sources/VLCMediaPlayer.m b/Sources/VLCMediaPlayer.m
index 804dd75d40c611a3e1ded887ca7c2cd68f967078..45dc2d310dc9a192c67512413fcbec401f8a4818 100644
--- a/Sources/VLCMediaPlayer.m
+++ b/Sources/VLCMediaPlayer.m
@@ -403,6 +403,7 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 
 /// Timer used to update time watch point interpolation on regular intervals
 @property (nonatomic) NSTimer *timeChangeUpdateTimer;
+@property (nonatomic) dispatch_queue_t timeChangeLockQueue;
 
 @end
 
@@ -438,6 +439,9 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 {
     if (self = [super init]) {
         _adjustFilter = [VLCAdjustFilter createWithVLCMediaPlayer:self];
+        _timeChangeLockQueue = dispatch_queue_create("org.videolan.vlcmediaplayer.timechangelock", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
+        _lastTimePoint.ts_us = -1;
+        _timeChangeUpdateInterval = 1.0;
     }
     return self;
 }
@@ -445,8 +449,6 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 - (instancetype)initWithLibrary:(VLCLibrary *)library
 {
     if (self = [self initCommon]) {
-        _lastTimePoint.ts_us = -1;
-        _timeChangeUpdateInterval = 1.0;
         _cachedState = VLCMediaPlayerStateStopped;
         _libVLCBackgroundQueue = [self libVLCBackgroundQueue];
         _minimalWatchTimePeriod = 500000;
@@ -466,8 +468,6 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 - (instancetype)initWithLibVLCInstance:(void *)playerInstance andLibrary:(VLCLibrary *)library
 {
     if (self = [self initCommon]) {
-        _lastTimePoint.ts_us = -1;
-        _timeChangeUpdateInterval = 1.0;
         _cachedState = VLCMediaPlayerStateStopped;
         _libVLCBackgroundQueue = [self libVLCBackgroundQueue];
         _minimalWatchTimePeriod = 500000;
@@ -510,6 +510,7 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 
 - (void)dealloc
 {
+    [self stopTimeChangeUpdateTimer];
     [self unregisterObservers];
 
     // Always get rid of the delegate first so we can stop sending messages to it
@@ -771,24 +772,36 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 
 - (VLCTime *)time
 {
-    NSAssert([NSThread isMainThread], @"Must be called from the main thread.");
+    __block libvlc_media_player_time_point_t lastTimePoint;
+    __block int64_t lastInterpolatedTime;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        lastTimePoint = _lastTimePoint;
+        lastInterpolatedTime = _lastInterpolatedTime;
+    });
 
-    if (_lastTimePoint.ts_us == -1) {
+    if (lastTimePoint.ts_us == -1) {
         return [VLCTime nullTime];
     }
 
-    return [VLCTime timeWithNumber:@(_lastInterpolatedTime / 1000)];
+    return [VLCTime timeWithNumber:@(lastInterpolatedTime / 1000)];
 }
 
 - (VLCTime *)remainingTime
 {
-    NSAssert([NSThread isMainThread], @"Must be called from the main thread.");
+    __block libvlc_media_player_time_point_t lastTimePoint;
+    __block int64_t lastInterpolatedTime;
+    __block double lastInterpolatedPosition;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        lastTimePoint = _lastTimePoint;
+        lastInterpolatedTime = _lastInterpolatedTime;
+        lastInterpolatedPosition = _lastInterpolatedPosition;
+    });
 
-    if (_lastTimePoint.position == 0. || _lastTimePoint.ts_us == -1) {
+    if (lastTimePoint.position == 0. || lastTimePoint.ts_us == -1) {
         return [VLCTime nullTime];
     }
     
-    double remaining = ((_lastInterpolatedTime / _lastInterpolatedPosition) - _lastInterpolatedTime) / 1000;
+    double remaining = ((lastInterpolatedTime / lastInterpolatedPosition) - lastInterpolatedTime) / 1000;
     return [VLCTime timeWithNumber:@(-remaining)];
 }
 
@@ -1028,17 +1041,23 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 #endif
 
 - (void)timeChangeUpdate {
-    if ( _lastTimePoint.ts_us == -1 ||
-        _timeDiscontinuityState ) {
-        return;
-    }
-
-    int64_t system_now_us = _systemDateOfDiscontinuity > 0 ? _systemDateOfDiscontinuity : libvlc_clock();
+    __block BOOL isChangeValid = YES;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        if ( _lastTimePoint.ts_us == -1 ||
+            _timeDiscontinuityState ) {
+            isChangeValid = NO;
+            return;
+        }
+        
+        int64_t system_now_us = _systemDateOfDiscontinuity > 0 ? _systemDateOfDiscontinuity : libvlc_clock();
 
-    libvlc_media_player_time_point_interpolate(&_lastTimePoint,
-                                               system_now_us,
-                                               &_lastInterpolatedTime,
-                                               &_lastInterpolatedPosition);
+        libvlc_media_player_time_point_interpolate(&_lastTimePoint,
+                                                   system_now_us,
+                                                   &_lastInterpolatedTime,
+                                                   &_lastInterpolatedPosition);
+    });
+    if(!isChangeValid)
+        return;
 
     [self willChangeValueForKey:@"time"];
     [self willChangeValueForKey:@"remainingTime"];
@@ -1059,6 +1078,25 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
     [self didChangeValueForKey:@"position"];
 }
 
+- (void)startTimeChangeUpdateTimer {
+    [self stopTimeChangeUpdateTimer];
+    __weak VLCMediaPlayer *weak_player = self;
+    _timeChangeUpdateTimer = [NSTimer timerWithTimeInterval:_timeChangeUpdateInterval repeats:YES block:^(NSTimer * _Nonnull timer) {
+        [weak_player timeChangeUpdate];
+    }];
+    CFRunLoopRef runloop = CFRunLoopGetMain();
+    CFRunLoopAddTimer(runloop, (__bridge CFRunLoopTimerRef)_timeChangeUpdateTimer, kCFRunLoopDefaultMode);
+}
+
+- (void)stopTimeChangeUpdateTimer {
+    CFRunLoopRef runloop = CFRunLoopGetMain();
+    if (_timeChangeUpdateTimer && CFRunLoopContainsTimer(runloop, (__bridge CFRunLoopTimerRef)_timeChangeUpdateTimer, kCFRunLoopDefaultMode)) {
+        [_timeChangeUpdateTimer fire];
+        CFRunLoopRemoveTimer(runloop, (__bridge CFRunLoopTimerRef)_timeChangeUpdateTimer, kCFRunLoopDefaultMode);
+    }
+}
+
+
 - (void)play
 {
     dispatch_async(_libVLCBackgroundQueue, ^{
@@ -1238,11 +1276,12 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 
 - (double)position
 {
-    NSAssert([NSThread isMainThread], @"Must be called from the main thread.");
-
-    
+    __block double position;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        position = _lastInterpolatedPosition;
+    });
     
-    return _lastInterpolatedPosition;
+    return position;
 }
 
 - (void)setPosition:(double)newPosition
@@ -1326,8 +1365,6 @@ static void HandleMediaPlayerRecord(const libvlc_event_t * event, void * opaque)
 - (instancetype)initWithDrawable:(id)aDrawable options:(NSArray *)options
 {
     if (self = [self initCommon]) {
-        _lastTimePoint.ts_us = -1;
-        _timeChangeUpdateInterval = 1.0;
         _cachedState = VLCMediaPlayerStateStopped;
         _libVLCBackgroundQueue = [self libVLCBackgroundQueue];
         _minimalWatchTimePeriod = 500000;
@@ -1439,18 +1476,24 @@ static const struct event_handler_entry
 
 - (void)mediaPlayerLastTimePointUpdated:(const libvlc_media_player_time_point_t)newTimePoint
 {
-    _timeDiscontinuityState = NO;
-    _systemDateOfDiscontinuity = 0;
-    _lastTimePoint = newTimePoint;
-    _lastInterpolatedTime = newTimePoint.ts_us;
-    _lastInterpolatedPosition = newTimePoint.position;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        _timeDiscontinuityState = NO;
+        _systemDateOfDiscontinuity = 0;
+        _lastTimePoint = newTimePoint;
+        _lastInterpolatedTime = newTimePoint.ts_us;
+        _lastInterpolatedPosition = newTimePoint.position;
+    });
 }
 
 - (void)mediaPlayerHandleTimeDiscontinuity:(int64_t)systemDate
 {
-    _systemDateOfDiscontinuity = systemDate;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        _systemDateOfDiscontinuity = systemDate;
+    });
     [self timeChangeUpdate];
-    _timeDiscontinuityState = YES;
+    dispatch_sync(_timeChangeLockQueue, ^{
+        _timeDiscontinuityState = YES;
+    });
 }
 
 - (void)mediaPlayerStateChanged:(const VLCMediaPlayerState)newState
@@ -1458,28 +1501,11 @@ static const struct event_handler_entry
     [self willChangeValueForKey:@"state"];
     _cachedState = newState;
     
-    __weak VLCMediaPlayer *weak_player = self;
-    dispatch_async(dispatch_get_main_queue(), ^{
-        __block VLCMediaPlayer *player = weak_player;
-        if (player == nil) {
-            return;
-        }
-        [player.timeChangeUpdateTimer invalidate];
-        [player timeChangeUpdate];
-        if (![player isPlaying])
-            return;
-        player.timeChangeUpdateTimer =
-            [NSTimer scheduledTimerWithTimeInterval:player.timeChangeUpdateInterval
-                                            repeats:YES
-                                              block:^(NSTimer * _Nonnull timer) {
-                player = weak_player;
-                if (player == nil) {
-                    [timer invalidate];
-                    return;
-                }
-                [player timeChangeUpdate];
-            }];
-    });
+    if ([self isPlaying]) {
+        [self startTimeChangeUpdateTimer];
+    } else {
+        [self stopTimeChangeUpdateTimer];
+    }
     
     [self didChangeValueForKey:@"state"];
 }