diff --git a/lazy_player/file_model.py b/lazy_player/file_model.py
index 698ed9e..7daeb16 100644
--- a/lazy_player/file_model.py
+++ b/lazy_player/file_model.py
@@ -8,6 +8,8 @@ from typing import Optional, overload
 
 from gi.repository import Gio, GObject
 
+from .thumbnailer import Thumbnailer
+
 
 class FileType(Enum):
     DIRECTORY = auto()
@@ -18,6 +20,8 @@ class FileItem(GObject.Object):
     file_type: FileType
     full_path: Path
     thumbnail: bytes
+    attempted_thumbnail: bool
+
     _has_thumbnail: bool
 
     __gtype_name__ = "FileItem"
@@ -28,6 +32,7 @@ class FileItem(GObject.Object):
         self.file_type = file_type
         self.full_path = full_path
         self.thumbnail = b""
+        self.attempted_thumbnail = False
         self._has_thumbnail = False
 
     @GObject.Property(type=GObject.TYPE_UINT64)
@@ -75,6 +80,13 @@ class FileItem(GObject.Object):
         self._has_thumbnail = value
         self.notify("has-thumbnail")
 
+    def ensure_thumbnail(self, thumbnailer: Thumbnailer):
+        if self.thumbnail or self.attempted_thumbnail:
+            return
+
+        if not self.attempted_thumbnail:
+            thumbnailer.generate_thumbnail(self)
+
     @overload
     def _load_attribute(self, name: str, dfl: str) -> str: ...
 
diff --git a/lazy_player/main_window.py b/lazy_player/main_window.py
index 8ffbd57..c00161f 100644
--- a/lazy_player/main_window.py
+++ b/lazy_player/main_window.py
@@ -246,21 +246,20 @@ class MainWindow(Gtk.ApplicationWindow):
         item.connect("notify::saved-duration", update_icon)
         update_icon()
 
-    def _refresh(self):
-        self._populate_file_list()
-
-        pos = self.selection_history.get(str(os.getcwd()), 0)
+    def _restore_selection(self):
+        pos = self.selection_history.get(os.getcwd(), 0)
         self.selection_model.set_selected(pos)
         self.list_view.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, None)
 
+    def _refresh(self):
+        self._populate_file_list()
+        self._restore_selection()
+
     def _navigate_to(self, path: Path):
         self.directory_history.append(Path(os.getcwd()))
         os.chdir(path)
         self._populate_file_list()
-
-        pos = self.selection_history.get(str(path), 0)
-        self.selection_model.set_selected(pos)
-        self.list_view.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, None)
+        self._restore_selection()
 
     def _navigate_back(self):
         if not self.directory_history:
@@ -269,10 +268,7 @@ class MainWindow(Gtk.ApplicationWindow):
         prev_dir = self.directory_history.pop()
         os.chdir(prev_dir)
         self._populate_file_list()
-
-        pos = self.selection_history.get(str(prev_dir), 0)
-        self.selection_model.set_selected(pos)
-        self.list_view.scroll_to(pos, Gtk.ListScrollFlags.SELECT | Gtk.ListScrollFlags.FOCUS, None)
+        self._restore_selection()
 
     def _on_selection_changed(
         self,
@@ -287,8 +283,7 @@ class MainWindow(Gtk.ApplicationWindow):
 
         self.selection_history[os.getcwd()] = position
 
-        file_item = self.selection
-        if file_item is not None:
+        if file_item := self.selection:
             # Update thumbnail if available
             if file_item.thumbnail:
                 gbytes = GLib.Bytes.new(cast(Any, file_item.thumbnail))
@@ -475,9 +470,9 @@ class MainWindow(Gtk.ApplicationWindow):
             self.last_position_save = frame_time
 
         # Update thumbnail if available
-        file_item = self.selection
+        if file_item := self.selection:
+            file_item.ensure_thumbnail(self.thumbnailer)
 
-        if file_item is not None:
             if file_item.thumbnail and not self.thumbnail_image.get_paintable():
                 gbytes = GLib.Bytes.new(cast(Any, file_item.thumbnail))
                 texture = Gdk.Texture.new_from_bytes(gbytes)
diff --git a/lazy_player/thumbnailer.py b/lazy_player/thumbnailer.py
index ed27715..dcf69ef 100644
--- a/lazy_player/thumbnailer.py
+++ b/lazy_player/thumbnailer.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import threading
 from queue import Empty, Queue
 
-from gi.repository import Gst
+from gi.repository import GLib, Gst
 
 from .file_model import FileItem
 
@@ -28,6 +28,7 @@ class Thumbnailer(threading.Thread):
             self.queue.get_nowait()
         except Empty:
             pass
+
         self.queue.put_nowait(file_item)
 
     def stop(self):
@@ -51,6 +52,7 @@ class Thumbnailer(threading.Thread):
             if file_item is None:
                 break
 
+            file_item.attempted_thumbnail = True
             self._generate_thumbnail(file_item)
 
     def _generate_thumbnail(self, file_item: FileItem):
@@ -105,11 +107,18 @@ class Thumbnailer(threading.Thread):
                 return
 
             try:
-                file_item.thumbnail = bytes(map_info.data)
-                file_item.has_thumbnail = True
+                thumbnail = bytes(map_info.data)
             finally:
                 buffer.unmap(map_info)
+
+            def set_thumbnail():
+                file_item.thumbnail = thumbnail
+                file_item.has_thumbnail = True
+
+            GLib.idle_add(set_thumbnail)
+
         except Exception as err:
             print("Failed:", file_item.full_path, err)
+
         finally:
             pipeline.set_state(Gst.State.NULL)