diff --git a/lazy_player/__init__.py b/lazy_player/__init__.py
index f089dbc..b2fbb53 100644
--- a/lazy_player/__init__.py
+++ b/lazy_player/__init__.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import os
 import sys
 from pathlib import Path
-from typing import Any, cast
+from typing import Any, TypeVar, cast, overload
 
 import gi
 
@@ -15,23 +15,28 @@ gi.require_version("Gst", "1.0")
 gi.require_version("Pango", "1.0")
 from gi.repository import Gdk, Gio, Gst, Gtk, Pango  # NOQA: E402
 
+_T = TypeVar("_T")
+
 
 class MainWindow(Gtk.ApplicationWindow):
     file_info_label: Gtk.Label
     stack: Gtk.Stack
     list_view: Gtk.ListView
     list_store: Gio.ListStore
+    selection_model: Gtk.SingleSelection
     video_widget: Gtk.Picture
     pipeline: Gst.Pipeline
     overlay_tick_callback_id: int
     overlay_label: Gtk.Label
     overlay_hide_time: float
+    last_position_save: float
 
     def __init__(self, *args: Any, **kwargs: Any):
         super().__init__(*args, **kwargs)
 
         # For overlay text timeout
         self.overlay_hide_time = 0.0
+        self.last_position_save = 0.0
         self.overlay_label = Gtk.Label()
         self.overlay_label.set_name("overlay-text")
         self.overlay_label.set_valign(Gtk.Align.CENTER)
@@ -131,13 +136,13 @@ class MainWindow(Gtk.ApplicationWindow):
         # Create list store and view
         self.list_store = Gio.ListStore(item_type=FileItem)
         self.list_view = Gtk.ListView()
-        selection_model = Gtk.SingleSelection.new(self.list_store)
-        selection_model.connect("selection-changed", self._on_selection_changed)
-        self.list_view.set_model(selection_model)
+        self.selection_model = Gtk.SingleSelection.new(self.list_store)
+        self.selection_model.connect("selection-changed", self._on_selection_changed)
+        self.list_view.set_model(self.selection_model)
         self.list_view.set_vexpand(True)
 
         def on_activate(widget: Gtk.ListView, index: int):
-            selected_item = selection_model.get_item(index)
+            selected_item = self.selection_model.get_item(index)
             if selected_item:
                 file_item = cast(FileItem, selected_item)
 
@@ -146,13 +151,45 @@ class MainWindow(Gtk.ApplicationWindow):
                     self._populate_file_list()
                     return
 
+                position = self._load_attribute("position", 0)
+
                 # Start playing the video
                 playbin = self.pipeline.get_by_name("playbin")
-                if playbin:
-                    playbin.set_property("uri", f"file://{os.path.abspath(file_item.full_path)}")
-                    self.pipeline.set_state(Gst.State.PLAYING)
-                    self.stack.set_visible_child_name("overlay")
-                    self.show_overlay_text(f"Playing: {file_item.name}")
+                if not playbin:
+                    return
+
+                full_path = os.path.abspath(file_item.full_path)
+                playbin.set_property("uri", f"file://{full_path}")
+
+                track = self._load_attribute("subtitle_track", -2)
+
+                if track >= 0:
+                    flags = playbin.get_property("flags")
+                    flags |= 0x00000004  # TEXT flag
+                    playbin.set_property("flags", flags)
+                    playbin.set_property("current-text", track)
+                elif track == -1:
+                    flags = playbin.get_property("flags")
+                    flags &= ~0x00000004  # TEXT flag
+                    playbin.set_property("flags", flags)
+
+                if position:
+                    # Pause and wait for it to complete.
+                    self.pipeline.set_state(Gst.State.PAUSED)
+                    self.pipeline.get_state(Gst.CLOCK_TIME_NONE)
+
+                    # Seek to saved position.
+                    self.pipeline.seek_simple(
+                        Gst.Format.TIME,
+                        Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
+                        position,
+                    )
+
+                # Now start playing
+                self.pipeline.set_state(Gst.State.PLAYING)
+
+                self.stack.set_visible_child_name("overlay")
+                self.show_overlay_text(f"Playing: {file_item.name}")
 
         self.list_view.connect("activate", on_activate)
 
@@ -172,6 +209,20 @@ class MainWindow(Gtk.ApplicationWindow):
 
         self.set_child(self.stack)
 
+    @property
+    def currently_playing(self):
+        selected_item = self.selection_model.get_selected_item()
+
+        if not selected_item:
+            return None
+
+        file_item = cast(FileItem, selected_item)
+
+        if file_item.file_type == FileType.DIRECTORY:
+            return None
+
+        return file_item.full_path
+
     def _setup_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem):
         # Create horizontal box to hold icon and label
         box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
@@ -232,6 +283,7 @@ class MainWindow(Gtk.ApplicationWindow):
             return True
 
         elif keyval == Gdk.keyval_from_name("Escape"):
+            self._save_position()
             self.pipeline.set_state(Gst.State.NULL)
             self.stack.set_visible_child_name("menu")
             self.list_view.grab_focus()
@@ -273,6 +325,7 @@ class MainWindow(Gtk.ApplicationWindow):
 
     def _seek_relative(self, offset: int) -> None:
         """Seek relative to current position by offset seconds"""
+
         playbin = self.pipeline.get_by_name("playbin")
         if not playbin:
             return
@@ -296,6 +349,7 @@ class MainWindow(Gtk.ApplicationWindow):
 
     def _get_subtitle_info(self, track_index: int) -> str:
         """Get subtitle track info including language if available"""
+
         playbin = self.pipeline.get_by_name("playbin")
         if not playbin:
             return str(track_index)
@@ -308,8 +362,56 @@ class MainWindow(Gtk.ApplicationWindow):
         found, lang = caps.get_string("language-code")
         return f"{track_index} ({lang})" if found else str(track_index)
 
+    def _save_position(self) -> None:
+        """Save current playback position as xattr"""
+
+        playbin = self.pipeline.get_by_name("playbin")
+        if not playbin:
+            return
+
+        success, position = self.pipeline.query_position(Gst.Format.TIME)
+        success2, duration = self.pipeline.query_duration(Gst.Format.TIME)
+
+        if success and success2:
+            self._save_attribute("position", position)
+            self._save_attribute("duration", duration)
+
+    def _save_attribute(self, name: str, value: str | float | int | None):
+        path = self.currently_playing
+
+        if path is None:
+            return
+
+        try:
+            if value is None:
+                os.removexattr(path, f"user.lazy_player.{name}")
+            else:
+                os.setxattr(path, f"user.lazy_player.{name}", str(value).encode("utf8"))
+        except OSError as err:
+            print(err, file=sys.stderr)
+
+    @overload
+    def _load_attribute(self, name: str, dfl: str) -> str: ...
+
+    @overload
+    def _load_attribute(self, name: str, dfl: int) -> int: ...
+
+    def _load_attribute(self, name: str, dfl: str | int) -> str | int:
+        path = self.currently_playing
+
+        if path is None:
+            return dfl
+
+        try:
+            strval = os.getxattr(path, f"user.lazy_player.{name}")
+            return type(dfl)(strval)
+        except OSError as err:
+            print(err, file=sys.stderr)
+            return dfl
+
     def _cycle_subtitles(self) -> None:
         """Cycle through available subtitle tracks, including off state"""
+
         playbin = self.pipeline.get_by_name("playbin")
         if not playbin:
             return
@@ -330,6 +432,7 @@ class MainWindow(Gtk.ApplicationWindow):
             playbin.set_property("current-text", 0)
             track_info = self._get_subtitle_info(0)
             self.show_overlay_text(f"Subtitle track: {track_info}")
+            self._save_attribute("subtitle_track", 0)
             return
 
         # If we're on the last track, disable subtitles
@@ -337,6 +440,7 @@ class MainWindow(Gtk.ApplicationWindow):
             flags &= ~0x00000004  # TEXT flag
             playbin.set_property("flags", flags)
             self.show_overlay_text("Subtitles: Off")
+            self._save_attribute("subtitle_track", -1)
             return
 
         # Otherwise cycle to next track
@@ -344,6 +448,7 @@ class MainWindow(Gtk.ApplicationWindow):
         playbin.set_property("current-text", next_track)
         track_info = self._get_subtitle_info(next_track)
         self.show_overlay_text(f"Subtitle track: {track_info}")
+        self._save_attribute("subtitle_track", next_track)
 
     def show_overlay_text(self, text: str, timeout_seconds: float = 1.0) -> None:
         """Show text in a centered overlay that disappears after timeout"""
@@ -358,6 +463,11 @@ class MainWindow(Gtk.ApplicationWindow):
         frame_time = frame_clock.get_frame_time() / 1_000_000  # Convert to seconds
         self.overlay_hide_time = frame_time + timeout_seconds
 
+        # Save position every 60 seconds
+        if frame_time - self.last_position_save >= 60.0:
+            self._save_position()
+            self.last_position_save = frame_time
+
     def _populate_file_list(self) -> None:
         # TODO: Implement proper version sort (strverscmp equivalent)