diff --git a/README.rst b/README.rst
index 01f8788..fbab918 100644
--- a/README.rst
+++ b/README.rst
@@ -64,6 +64,7 @@ Supported tags
 * #EXT-X-PART
 * #EXT-X-RENDITION-REPORT
 * #EXT-X-SKIP
+* #EXT-X-SESSION-DATA
 
 Encryption keys
 ---------------
diff --git a/m3u8/model.py b/m3u8/model.py
index 05353b0..2792d0e 100644
--- a/m3u8/model.py
+++ b/m3u8/model.py
@@ -195,6 +195,10 @@ class M3U8(object):
         self.rendition_reports = RenditionReportList([ RenditionReport(base_uri=self.base_uri, **rendition_report)
                                                   for rendition_report in self.data.get('rendition_reports', []) ])
 
+        self.session_data = SessionDataList([ SessionData(**session_data)
+                             for session_data in self.data.get('session_data', [])
+                             if 'data_id' in session_data ])
+
     def __unicode__(self):
         return self.dumps()
 
@@ -295,6 +299,8 @@ class M3U8(object):
             output.append(str(self.part_inf))
         if self.skip:
             output.append(str(self.skip))
+        if self.session_data:
+            output.append(str(self.session_data))
 
         output.append(str(self.segments))
 
@@ -805,22 +811,26 @@ class Media(BasePathMixin):
         return self.dumps()
 
 
-class MediaList(list, GroupedBasePathMixin):
+class TagList(list):
 
     def __str__(self):
-        output = [str(playlist) for playlist in self]
+        output = [str(tag) for tag in self]
         return '\n'.join(output)
 
+
+class MediaList(TagList, GroupedBasePathMixin):
+
     @property
     def uri(self):
         return [media.uri for media in self]
 
 
-class PlaylistList(list, GroupedBasePathMixin):
+class PlaylistList(TagList, GroupedBasePathMixin):
+    pass
 
-    def __str__(self):
-        output = [str(playlist) for playlist in self]
-        return '\n'.join(output)
+
+class SessionDataList(TagList):
+    pass
 
 
 class Start(object):
@@ -913,6 +923,28 @@ class PartInformation(object):
     def __str__(self):
         return self.dumps()
 
+class SessionData(object):
+    def __init__(self, data_id, value=None, uri=None, language=None):
+        self.data_id = data_id
+        self.value = value
+        self.uri = uri
+        self.language = language
+
+    def dumps(self):
+        session_data_out = ['DATA-ID=' + quoted(self.data_id)]
+
+        if self.value:
+            session_data_out.append('VALUE=' + quoted(self.value))
+        elif self.uri:
+            session_data_out.append('URI=' + quoted(self.uri))
+        if self.language:
+            session_data_out.append('LANGUAGE=' + quoted(self.language))
+
+        return '#EXT-X-SESSION-DATA:' + ','.join(session_data_out)
+
+    def __str__(self):
+        return self.dumps()
+
 def find_key(keydata, keylist):
     if not keydata:
         return None
diff --git a/m3u8/parser.py b/m3u8/parser.py
index 2bc9534..a79f105 100644
--- a/m3u8/parser.py
+++ b/m3u8/parser.py
@@ -53,7 +53,8 @@ def parse(content, strict=False, custom_tags_parser=None):
         'keys': [],
         'rendition_reports': [],
         'skip': {},
-        'part_inf': {}
+        'part_inf': {},
+        'session_data': []
     }
 
     state = {
@@ -169,6 +170,9 @@ def parse(content, strict=False, custom_tags_parser=None):
         elif line.startswith(protocol.ext_x_skip):
             _parse_skip(line, data, state)
 
+        elif line.startswith(protocol.ext_x_session_data):
+            _parse_session_data(line, data, state)
+
         # Comments and whitespace
         elif line.startswith('#'):
             if callable(custom_tags_parser):
@@ -407,6 +411,11 @@ def _parse_skip(line, data, state):
 
     data['skip'] = _parse_attribute_list(protocol.ext_x_skip, line, attribute_parser)
 
+def _parse_session_data(line, data, state):
+    quoted = remove_quotes_parser('data_id', 'value', 'uri', 'language')
+    session_data = _parse_attribute_list(protocol.ext_x_session_data, line, quoted)
+    data['session_data'].append(session_data)
+
 def string_to_lines(string):
     return string.strip().splitlines()
 
diff --git a/m3u8/protocol.py b/m3u8/protocol.py
index bbb724e..3933e80 100644
--- a/m3u8/protocol.py
+++ b/m3u8/protocol.py
@@ -33,3 +33,4 @@ ext_x_part_inf = '#EXT-X-PART-INF'
 ext_x_part = '#EXT-X-PART'
 ext_x_rendition_report = '#EXT-X-RENDITION-REPORT'
 ext_x_skip = '#EXT-X-SKIP'
+ext_x_session_data = '#EXT-X-SESSION-DATA'

diff --git a/tests/playlists.py b/tests/playlists.py
index 26e9cd1..04324af 100755
--- a/tests/playlists.py
+++ b/tests/playlists.py
@@ -802,4 +802,17 @@ http://example.com/hi.m3u8
 http://example.com/audio-only.m3u8
 '''
 
+SESSION_DATA_PLAYLIST = '''#EXTM3U
+#EXT-X-VERSION:4
+#EXT-X-SESSION-DATA:DATA-ID="com.example.value",VALUE="example",LANGUAGE="en"
+'''
+
+MULTIPLE_SESSION_DATA_PLAYLIST = '''#EXTM3U
+#EXT-X-VERSION:4
+#EXT-X-SESSION-DATA:DATA-ID="com.example.value",VALUE="example",LANGUAGE="en"
+#EXT-X-SESSION-DATA:DATA-ID="com.example.value",VALUE="example",LANGUAGE="ru"
+#EXT-X-SESSION-DATA:DATA-ID="com.example.value",VALUE="example",LANGUAGE="de"
+#EXT-X-SESSION-DATA:DATA-ID="com.example.title",URI="title.json"
+'''
+
 del abspath, dirname, join
diff --git a/tests/test_model.py b/tests/test_model.py
index f6bf956..f1dea64 100755
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -15,7 +15,7 @@ from m3u8.protocol import ext_x_start, ext_x_part
 
 import m3u8
 import playlists
-from m3u8.model import Segment, Key, Media, MediaList, RenditionReport, PartialSegment, denormalize_attribute, find_key
+from m3u8.model import Segment, Key, Media, MediaList, RenditionReport, PartialSegment, denormalize_attribute, find_key, SessionData
 
 
 class UTC(datetime.tzinfo):
@@ -631,6 +631,18 @@ def test_should_dump_complex_unencrypted_encrypted_keys_no_uri_attr():
 
     assert expected == obj.dumps().strip()
 
+def test_should_dump_session_data():
+    obj = m3u8.M3U8(playlists.SESSION_DATA_PLAYLIST)
+    expected = playlists.SESSION_DATA_PLAYLIST.strip()
+
+    assert expected == obj.dumps().strip()
+
+def test_should_dump_multiple_session_data():
+    obj = m3u8.M3U8(playlists.MULTIPLE_SESSION_DATA_PLAYLIST)
+    expected = playlists.MULTIPLE_SESSION_DATA_PLAYLIST.strip()
+
+    assert expected == obj.dumps().strip()
+
 
 
 def test_length_segments_by_key():
@@ -1002,6 +1014,43 @@ def test_partial_segment_gap_and_byterange():
 
     assert result == expected
 
+def test_session_data_with_value():
+    obj = SessionData(
+        'com.example.value',
+        'example',
+        language='en'
+    )
+
+    result = obj.dumps()
+    expected = '#EXT-X-SESSION-DATA:DATA-ID="com.example.value",VALUE="example",LANGUAGE="en"'
+
+    assert result == expected
+
+def test_session_data_with_uri():
+    obj = SessionData(
+        'com.example.value',
+        uri='example.json',
+        language='en'
+    )
+
+    result = obj.dumps()
+    expected = '#EXT-X-SESSION-DATA:DATA-ID="com.example.value",URI="example.json",LANGUAGE="en"'
+
+    assert result == expected
+
+def test_session_data_cannot_be_created_with_value_and_uri_at_the_same_time():
+    obj = SessionData(
+        'com.example.value',
+        value='example',
+        uri='example.json',
+        language='en'
+    )
+
+    result = obj.dumps()
+    expected = '#EXT-X-SESSION-DATA:DATA-ID="com.example.value",VALUE="example",LANGUAGE="en"'
+
+    assert result == expected
+
 def test_endswith_newline():
     obj = m3u8.loads(playlists.SIMPLE_PLAYLIST)
 
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 71be2d8..912d408 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -317,6 +317,34 @@ def test_should_parse_start_with_precise():
     assert data['start']['time_offset'] == 10.5
     assert data['start']['precise'] == 'YES'
 
+
+def test_should_parse_session_data():
+    data = m3u8.parse(playlists.SESSION_DATA_PLAYLIST)
+    assert data['session_data'][0]['data_id'] == 'com.example.value'
+    assert data['session_data'][0]['value'] == 'example'
+    assert data['session_data'][0]['language'] == 'en'
+
+
+def test_should_parse_multiple_session_data():
+    data = m3u8.parse(playlists.MULTIPLE_SESSION_DATA_PLAYLIST)
+
+    assert len(data['session_data']) == 4
+
+    assert data['session_data'][0]['data_id'] == 'com.example.value'
+    assert data['session_data'][0]['value'] == 'example'
+    assert data['session_data'][0]['language'] == 'en'
+
+    assert data['session_data'][1]['data_id'] == 'com.example.value'
+    assert data['session_data'][1]['value'] == 'example'
+    assert data['session_data'][1]['language'] == 'ru'
+
+    assert data['session_data'][2]['data_id'] == 'com.example.value'
+    assert data['session_data'][2]['value'] == 'example'
+    assert data['session_data'][2]['language'] == 'de'
+
+    assert data['session_data'][3]['data_id'] == 'com.example.title'
+    assert data['session_data'][3]['uri'] == 'title.json'
+
 def test_simple_playlist_with_discontinuity_sequence():
     data = m3u8.parse(playlists.SIMPLE_PLAYLIST_WITH_DISCONTINUITY_SEQUENCE)
     assert data['discontinuity_sequence'] == 123

Add support for EXT-X-SESSION-DATA tag
`The EXT-X-SESSION-DATA tag allows arbitrary session data to be
   carried in a Master Playlist.`
https://tools.ietf.org/html/rfc8216#section-4.3.4.4
tests/test_model.py::test_target_duration_attribute tests/test_model.py::test_media_sequence_attribute tests/test_model.py::test_program_date_time_attribute tests/test_model.py::test_program_date_time_attribute_for_each_segment tests/test_model.py::test_program_date_time_attribute_with_discontinuity tests/test_model.py::test_program_date_time_attribute_without_discontinuity tests/test_model.py::test_segment_discontinuity_attribute tests/test_model.py::test_segment_cue_out_attribute tests/test_model.py::test_segment_cue_out_dumps tests/test_model.py::test_segment_elemental_scte35_attribute tests/test_model.py::test_segment_envivio_scte35_attribute tests/test_model.py::test_segment_unknown_scte35_attribute tests/test_model.py::test_keys_on_clear_playlist tests/test_model.py::test_keys_on_simple_encrypted_playlist tests/test_model.py::test_key_attribute tests/test_model.py::test_key_attribute_on_none tests/test_model.py::test_key_attribute_without_initialization_vector tests/test_model.py::test_segments_attribute tests/test_model.py::test_segments_attribute_without_title tests/test_model.py::test_segments_attribute_without_duration tests/test_model.py::test_segments_attribute_with_byterange tests/test_model.py::test_segment_attribute_with_multiple_keys tests/test_model.py::test_segment_title_dumps tests/test_model.py::test_is_variant_attribute tests/test_model.py::test_is_endlist_attribute tests/test_model.py::test_is_i_frames_only_attribute tests/test_model.py::test_playlists_attribute tests/test_model.py::test_playlists_attribute_without_program_id tests/test_model.py::test_playlists_attribute_with_resolution tests/test_model.py::test_iframe_playlists_attribute tests/test_model.py::test_version_attribute tests/test_model.py::test_allow_cache_attribute tests/test_model.py::test_files_attribute_should_list_all_files_including_segments_and_key tests/test_model.py::test_vod_playlist_type_should_be_imported_as_a_simple_attribute tests/test_model.py::test_event_playlist_type_should_be_imported_as_a_simple_attribute tests/test_model.py::test_independent_segments_should_be_true tests/test_model.py::test_independent_segments_should_be_false tests/test_model.py::test_no_playlist_type_leaves_attribute_empty tests/test_model.py::test_dump_playlists_with_resolution tests/test_model.py::test_dump_should_build_file_with_same_content tests/test_model.py::test_dump_should_create_sub_directories tests/test_model.py::test_dump_should_raise_if_create_sub_directories_fails tests/test_model.py::test_dump_should_work_for_variant_streams tests/test_model.py::test_dump_should_work_for_variant_playlists_with_iframe_playlists tests/test_model.py::test_dump_should_work_for_iframe_playlists tests/test_model.py::test_dump_should_include_program_date_time tests/test_model.py::test_dump_should_not_ignore_zero_duration tests/test_model.py::test_dump_should_include_segment_level_program_date_time tests/test_model.py::test_dump_should_include_segment_level_program_date_time_without_discontinuity tests/test_model.py::test_dump_should_include_map_attributes tests/test_model.py::test_dump_should_work_for_playlists_using_byteranges tests/test_model.py::test_should_dump_with_endlist_tag tests/test_model.py::test_should_dump_without_endlist_tag tests/test_model.py::test_should_dump_multiple_keys tests/test_model.py::test_should_dump_unencrypted_encrypted_keys_together tests/test_model.py::test_should_dump_complex_unencrypted_encrypted_keys tests/test_model.py::test_should_dump_complex_unencrypted_encrypted_keys_no_uri_attr tests/test_model.py::test_should_dump_session_data tests/test_model.py::test_should_dump_multiple_session_data tests/test_model.py::test_length_segments_by_key tests/test_model.py::test_list_segments_by_key tests/test_model.py::test_replace_segment_key tests/test_model.py::test_keyformat_and_keyformatversion tests/test_model.py::test_should_dump_program_datetime_and_discontinuity tests/test_model.py::test_should_normalize_segments_and_key_urls_if_base_path_passed_to_constructor tests/test_model.py::test_should_normalize_variant_streams_urls_if_base_path_passed_to_constructor tests/test_model.py::test_should_normalize_segments_and_key_urls_if_base_path_attribute_updated tests/test_model.py::test_playlist_type_dumped_to_appropriate_m3u8_field tests/test_model.py::test_empty_playlist_type_is_gracefully_ignored tests/test_model.py::test_none_playlist_type_is_gracefully_ignored tests/test_model.py::test_0_media_sequence_added_to_file tests/test_model.py::test_none_media_sequence_gracefully_ignored tests/test_model.py::test_0_discontinuity_sequence_added_to_file tests/test_model.py::test_none_discontinuity_sequence_gracefully_ignored tests/test_model.py::test_non_zero_discontinuity_sequence_added_to_file tests/test_model.py::test_should_correctly_update_base_path_if_its_blank tests/test_model.py::test_base_path_should_just_return_uri_if_absolute tests/test_model.py::test_m3u8_should_propagate_base_uri_to_segments tests/test_model.py::test_m3u8_should_propagate_base_uri_to_key tests/test_model.py::test_base_path_with_optional_uri_should_do_nothing tests/test_model.py::test_medialist_uri_method tests/test_model.py::test_segment_map_uri_attribute tests/test_model.py::test_segment_map_uri_attribute_with_byterange tests/test_model.py::test_start_with_negative_offset tests/test_model.py::test_start_with_precise tests/test_model.py::test_playlist_stream_info_contains_group_id_refs tests/test_model.py::test_should_dump_frame_rate tests/test_model.py::test_add_segment_to_playlist tests/test_model.py::test_segment_str_method tests/test_model.py::test_attribute_denormaliser tests/test_model.py::test_find_key_throws_when_no_match tests/test_model.py::test_ll_playlist tests/test_model.py::test_add_rendition_report_to_playlist tests/test_model.py::test_add_part_to_segment tests/test_model.py::test_partial_segment_gap_and_byterange tests/test_model.py::test_session_data_with_value tests/test_model.py::test_session_data_with_uri tests/test_model.py::test_session_data_cannot_be_created_with_value_and_uri_at_the_same_time tests/test_model.py::test_endswith_newline tests/test_parser.py::test_should_parse_simple_playlist_from_string tests/test_parser.py::test_should_parse_non_integer_duration_from_playlist_string tests/test_parser.py::test_should_parse_comma_in_title tests/test_parser.py::test_should_parse_simple_playlist_from_string_with_different_linebreaks tests/test_parser.py::test_should_parse_sliding_window_playlist_from_string tests/test_parser.py::test_should_parse_playlist_with_encripted_segments_from_string tests/test_parser.py::test_should_load_playlist_with_iv_from_string tests/test_parser.py::test_should_add_key_attribute_to_segment_from_playlist tests/test_parser.py::test_should_add_non_key_for_multiple_keys_unencrypted_and_encrypted tests/test_parser.py::test_should_handle_key_method_none_and_no_uri_attr tests/test_parser.py::test_should_parse_quoted_title_from_playlist tests/test_parser.py::test_should_parse_unquoted_title_from_playlist tests/test_parser.py::test_should_parse_variant_playlist tests/test_parser.py::test_should_parse_variant_playlist_with_cc_subtitles_and_audio tests/test_parser.py::test_should_parse_variant_playlist_with_average_bandwidth tests/test_parser.py::test_should_parse_variant_playlist_with_bandwidth_as_float tests/test_parser.py::test_should_parse_variant_playlist_with_iframe_playlists tests/test_parser.py::test_should_parse_variant_playlist_with_alt_iframe_playlists_layout tests/test_parser.py::test_should_parse_iframe_playlist tests/test_parser.py::test_should_parse_playlist_using_byteranges tests/test_parser.py::test_should_parse_endlist_playlist tests/test_parser.py::test_should_parse_ALLOW_CACHE tests/test_parser.py::test_should_parse_VERSION tests/test_parser.py::test_should_parse_program_date_time_from_playlist tests/test_parser.py::test_should_parse_scte35_from_playlist tests/test_parser.py::test_should_parse_envivio_cue_playlist tests/test_parser.py::test_parse_simple_playlist_messy tests/test_parser.py::test_parse_simple_playlist_messy_strict tests/test_parser.py::test_commaless_extinf tests/test_parser.py::test_commaless_extinf_strict tests/test_parser.py::test_should_parse_segment_map_uri tests/test_parser.py::test_should_parse_segment_map_uri_with_byterange tests/test_parser.py::test_should_parse_empty_uri_with_base_path tests/test_parser.py::test_should_parse_start_with_negative_time_offset tests/test_parser.py::test_should_parse_start_with_precise tests/test_parser.py::test_should_parse_session_data tests/test_parser.py::test_should_parse_multiple_session_data tests/test_parser.py::test_simple_playlist_with_discontinuity_sequence tests/test_parser.py::test_simple_playlist_with_custom_tags tests/test_parser.py::test_master_playlist_with_frame_rate tests/test_parser.py::test_low_latency_playlist