diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,9 @@
+# Version 0.5.0.0 (December 1, 2017)
+
+  * Add support for track length (thanks to Catherine Galkina)
+
+  * Widen dependency versions to latest available.
+
 # Version 0.4.1.0 (February 5, 2017)
 
   * Remove redundant constraints as reported by GHC 8.X.
diff --git a/playlists.cabal b/playlists.cabal
--- a/playlists.cabal
+++ b/playlists.cabal
@@ -1,5 +1,5 @@
 name:          playlists
-version:       0.4.1.0
+version:       0.5.0.0
 synopsis:      Library and executable for working with playlist files.
 homepage:      https://github.com/pjones/playlists
 license:       BSD3
@@ -10,7 +10,7 @@
 category:      Text
 build-type:    Simple
 cabal-version: >= 1.18
-tested-with:   GHC==7.10.3, GHC==8.0.1
+tested-with:   GHC==7.10.3, GHC==8.0.2
 description:
   Playlists is a library for working with media playlist files.  The
   original motivation for the library was extracting URLs for
@@ -104,7 +104,7 @@
 
   build-depends: base
                , bytestring
-               , optparse-applicative >= 0.10.0 && < 0.14.0
+               , optparse-applicative >= 0.10 && < 0.15
                , playlists
                , text
 
diff --git a/src/Text/Playlist/M3U/Reader.hs b/src/Text/Playlist/M3U/Reader.hs
--- a/src/Text/Playlist/M3U/Reader.hs
+++ b/src/Text/Playlist/M3U/Reader.hs
@@ -16,11 +16,12 @@
 
 --------------------------------------------------------------------------------
 import Control.Applicative
-import Control.Monad (msum, void)
+import Control.Monad (void)
 import Data.Attoparsec.ByteString
+import Data.Attoparsec.ByteString.Char8 (signed, double)
+import Data.Maybe (catMaybes)
 import Data.Text (Text)
 import Data.Text.Encoding (decodeUtf8)
-import Data.Word8 (isDigit)
 import Text.Playlist.Internal.Attoparsec
 import Text.Playlist.Types
 
@@ -29,21 +30,25 @@
 parsePlaylist :: Parser Playlist
 parsePlaylist = do
   ts <- many1 parseTrack
-  void (many' commentOrTitle) -- Trailing comments.
+  void (many' commentOrDirective) -- Trailing comments.
   return ts
 
 --------------------------------------------------------------------------------
 -- | Parser for a single track in a M3U file.
 parseTrack :: Parser Track
 parseTrack = do
-  -- Get the title closest to the URL or Nothing.
-  title <- msum . reverse <$> many' commentOrTitle
+  -- Get the length and title closest to the URL or Nothing.
+  (title, len) <- maybeTitleAndLength . reverse <$> (many' commentOrDirective)
   url   <- parseURL
-
-  return Track { trackURL   = url
-               , trackTitle = title
+  return Track { trackURL      = url
+               , trackTitle    = title
+               , trackDuration = len
                }
-
+    where
+      maybeTitleAndLength lst =
+        case catMaybes lst of
+          []    -> (Nothing, Nothing)
+          x : _ -> x
 --------------------------------------------------------------------------------
 -- | Parser for URL or file name in a M3U file.  The URL is the entire
 -- line so this parser extracts the entire line and decodes it.
@@ -54,21 +59,21 @@
 -- | Comment parser with a twist.  In the extended M3U format metadata
 -- for a track can be placed in a comment that appears just before the
 -- URL.  This parser succeeds if the current line is a comment, and
--- always skips over the entire comment.  If the comment represents a
--- track title then that information will be returned in a @Just@.  If
--- it's just a regular comment then @Nothing@ is returned.
-commentOrTitle :: Parser (Maybe Text)
-commentOrTitle = do
-    skipSpace
-    skip (== 35) -- Comment character "#"
-    istitle <- (string "EXTINF:" >> return True) <|> return False
-    if istitle then title <|> comment else comment
-  where
-    comment = skipLine >> return Nothing
-    title   = do skip (== 45) <|> return () -- Skip optional negative sign.
-                 skipWhile isDigit          -- Skip length.
-                 skip (== 44)               -- Skip comma.
-                 skipSpace
-                 text <- decodeUtf8 <$> takeWhile1 (not . isEOL)
-                 skipSpace
-                 return (Just text)
+-- always skips over the entire comment.  If the comment represents an
+-- EXTINF directive then that information will be returned in a @Just@.
+-- If it's just a regular comment then @Nothing@ is returned.
+commentOrDirective :: Parser (Maybe (Maybe Text, Maybe Float))
+commentOrDirective = do
+  skipSpace
+  skip (== 35) -- Comment character "#"
+  isDirective <- (string "EXTINF:" >> return True) <|> return False
+  if isDirective then directive <|> comment else comment
+    where
+      comment   = skipLine >> return Nothing
+      directive = do
+        mlen <- (Just . realToFrac <$> signed double) <|> return Nothing -- Parse length.
+        skip (== 44)                                                     -- Skip comma.
+        mtext <- (Just . decodeUtf8 <$> takeWhile1 (not . isEOL)) <|> (return Nothing)
+        skipLine
+        return (Just (mtext, mlen))
+
diff --git a/src/Text/Playlist/M3U/Writer.hs b/src/Text/Playlist/M3U/Writer.hs
--- a/src/Text/Playlist/M3U/Writer.hs
+++ b/src/Text/Playlist/M3U/Writer.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 
 {-
 
@@ -29,14 +30,27 @@
 --------------------------------------------------------------------------------
 writeTrack :: Track -> Builder
 writeTrack x =
-  writeTitle (trackTitle x)              <>
+  writeTitleAndLength x <>
   B.byteString (encodeUtf8 $ trackURL x) <>
   B.charUtf8 '\n'
 
 --------------------------------------------------------------------------------
-writeTitle :: Maybe Text -> Builder
-writeTitle Nothing  = mempty
-writeTitle (Just x) =
-  B.byteString "#EXTINF:-1,"  <>
-  B.byteString (encodeUtf8 x) <>
+writeTitleAndLength :: Track -> Builder
+writeTitleAndLength (Track _ Nothing Nothing) = mempty
+writeTitleAndLength Track{..} =
+  B.byteString "#EXTINF:"   <>
+  writeLength trackDuration <>
+  B.byteString ","          <>
+  writeTitle trackTitle     <>
   B.charUtf8 '\n'
+
+--------------------------------------------------------------------------------
+writeLength :: Maybe Float -> Builder
+writeLength Nothing = mempty
+writeLength (Just l) = B.stringUtf8 (show l)
+
+--------------------------------------------------------------------------------
+writeTitle :: Maybe Text -> Builder
+writeTitle Nothing = mempty
+writeTitle (Just x) = B.byteString (encodeUtf8 x)
+
diff --git a/src/Text/Playlist/PLS/Reader.hs b/src/Text/Playlist/PLS/Reader.hs
--- a/src/Text/Playlist/PLS/Reader.hs
+++ b/src/Text/Playlist/PLS/Reader.hs
@@ -18,6 +18,7 @@
 import Control.Applicative
 import Control.Monad (void)
 import Data.Attoparsec.ByteString
+import Data.Attoparsec.ByteString.Char8 (signed, double)
 import Data.ByteString (ByteString)
 import Data.Text (Text)
 import Data.Text.Encoding (decodeUtf8)
@@ -50,11 +51,13 @@
   (n, url) <- parseFileN
   title    <- (Just <$> parseTitle n) <|> return Nothing
 
-  -- Skip optional length field, we don't use it.
-  (skipSpace >> string "Length" >> skipLine) <|> return ()
+  -- Parse track length.
+  mlen <- (Just . realToFrac <$> (skipSpace >> string "Length" >> skipWhile isDigit >> string "=" >> signed double))
+      <|> return Nothing
 
-  return Track { trackURL   = url
-               , trackTitle = title
+  return Track { trackURL      = url
+               , trackTitle    = title
+               , trackDuration = mlen
                }
 
 --------------------------------------------------------------------------------
diff --git a/src/Text/Playlist/PLS/Writer.hs b/src/Text/Playlist/PLS/Writer.hs
--- a/src/Text/Playlist/PLS/Writer.hs
+++ b/src/Text/Playlist/PLS/Writer.hs
@@ -35,7 +35,7 @@
 
 --------------------------------------------------------------------------------
 writeTrack :: (Track, Int) -> Builder
-writeTrack x = writeFileN x <> writeTitle x
+writeTrack x = writeFileN x <> writeTitle x <> writeLength x
 
 --------------------------------------------------------------------------------
 writeFileN :: (Track, Int) -> Builder
@@ -56,3 +56,15 @@
                B.charUtf8 '='              <>
                B.byteString (encodeUtf8 y) <>
                B.charUtf8 '\n'
+
+--------------------------------------------------------------------------------
+writeLength :: (Track, Int) -> Builder
+writeLength (x, n) =
+  case trackDuration x of
+    Nothing -> mempty
+    Just l  -> B.byteString "Length" <>
+               B.stringUtf8 (show n) <>
+               B.charUtf8 '='        <>
+               B.stringUtf8 (show l) <>
+               B.charUtf8 '\n'
+
diff --git a/src/Text/Playlist/Types.hs b/src/Text/Playlist/Types.hs
--- a/src/Text/Playlist/Types.hs
+++ b/src/Text/Playlist/Types.hs
@@ -22,8 +22,9 @@
 --------------------------------------------------------------------------------
 -- | A single music file or streaming URL.
 data Track = Track
-  { trackURL   :: Text       -- ^ URL for a file or streaming resource.
-  , trackTitle :: Maybe Text -- ^ Optional title.
+  { trackURL      :: Text        -- ^ URL for a file or streaming resource.
+  , trackTitle    :: Maybe Text  -- ^ Optional title.
+  , trackDuration :: Maybe Float -- ^ Optional duration in seconds.
   } deriving (Show, Eq)
 
 --------------------------------------------------------------------------------
diff --git a/test/Examples.hs b/test/Examples.hs
--- a/test/Examples.hs
+++ b/test/Examples.hs
@@ -29,14 +29,17 @@
 secretAgent =
   [ Track { trackURL   = "http://mp2.somafm.com:9016"
           , trackTitle = Just "SomaFM: Secret Agent (#1 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"
+          , trackDuration = Just (-1)
           }
 
   , Track { trackURL   = "http://mp3.somafm.com:443"
           , trackTitle = Just "SomaFM: Secret Agent (#2 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"
+          , trackDuration = Just (-1)
           }
 
   , Track { trackURL   = "http://ice.somafm.com/secretagent"
           , trackTitle = Just "SomaFM: Secret Agent (Firewall-friendly 128k mp3) The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"
+          , trackDuration = Just (-1)
           }
   ]
 
@@ -45,10 +48,12 @@
 pigRadio =
   [ Track { trackURL   = "http://s6.mediastreaming.it:8080"
           , trackTitle = Just "Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7"
+          , trackDuration = Just 0
           }
 
   , Track { trackURL   = "http://s1.viastreaming.net:7480"
           , trackTitle = Just "Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7"
+          , trackDuration = Just (-1)
           }
   ]
 
@@ -57,6 +62,7 @@
 utf8Radio =
   [ Track { trackURL   = "http://fake.com"
           , trackTitle = Just "Alle otto i bambini erano già in costume da bagno"
+          , trackDuration = Nothing
           }
   ]
 
@@ -65,17 +71,19 @@
 hitParty =
   [ Track { trackURL   = "http://firewall.hitparty.net:443"
           , trackTitle = Just "(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits"
+          , trackDuration = Just (-1)
           }
   , Track { trackURL   = "http://icecast.pulsradio.com:80/hitpartyHD.mp3.m3u"
           , trackTitle = Just "(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits"
+          , trackDuration = Just (-1)
           }
   ]
 
 --------------------------------------------------------------------------------
 aaFile :: Playlist
 aaFile =
-  [ Track { trackURL = "http://foo.com", trackTitle = Nothing}
-  , Track { trackURL = "http://bar.com", trackTitle = Nothing}
+  [ Track { trackURL = "http://foo.com", trackTitle = Nothing, trackDuration = Nothing}
+  , Track { trackURL = "http://bar.com", trackTitle = Nothing, trackDuration = Nothing}
   ]
 
 --------------------------------------------------------------------------------
diff --git a/test/ResolveSpec.hs b/test/ResolveSpec.hs
--- a/test/ResolveSpec.hs
+++ b/test/ResolveSpec.hs
@@ -42,7 +42,7 @@
 
 --------------------------------------------------------------------------------
 resolved :: Playlist
-resolved = [ Track "http://foo.com"             Nothing
-           , Track "http://bar.com"             Nothing
-           , Track "http://foo.com/aa.baz.live" Nothing
+resolved = [ Track "http://foo.com"             Nothing Nothing
+           , Track "http://bar.com"             Nothing Nothing
+           , Track "http://foo.com/aa.baz.live" Nothing Nothing
            ]
