diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 878e0cc80a99ccd56346a7eea903f3016017ab70..9e1209c07c325c017bc88e4c2b92f9f821b23220 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -153,6 +153,14 @@ $routes->group(
                             'filter' => 'permission:podcasts-view,podcast-view',
                         ]
                     );
+                    $routes->get(
+                        'listening-time',
+                        'Podcast::viewAnalyticsListeningTime/$1',
+                        [
+                            'as' => 'podcast-analytics-listening-time',
+                            'filter' => 'permission:podcasts-view,podcast-view',
+                        ]
+                    );
                     $routes->get(
                         'players',
                         'Podcast::viewAnalyticsPlayers/$1',
diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php
index 2cf43a4736140e26604999dfc36a63836fe9e3e1..1d10f9f2f0b6a92c25100a979609727f3d927063 100644
--- a/app/Controllers/Admin/Podcast.php
+++ b/app/Controllers/Admin/Podcast.php
@@ -90,6 +90,14 @@ class Podcast extends BaseController
         return view('admin/podcast/analytics/unique_listeners', $data);
     }
 
+    public function viewAnalyticsListeningTime()
+    {
+        $data = ['podcast' => $this->podcast];
+
+        replace_breadcrumb_params([0 => $this->podcast->title]);
+        return view('admin/podcast/analytics/listening-time', $data);
+    }
+
     public function viewAnalyticsPlayers()
     {
         $data = ['podcast' => $this->podcast];
diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php
index dba5b87679ca143b365ef6c044f8508312e268f7..be49e1059ead0536ac1b8171e48e970d26a66269 100644
--- a/app/Language/en/Breadcrumb.php
+++ b/app/Language/en/Breadcrumb.php
@@ -27,4 +27,5 @@ return [
     'webpages' => 'Web pages',
     'unique-listeners' => 'Unique listeners',
     'players' => 'Players',
+    'listening-time' => 'Listening time',
 ];
diff --git a/app/Language/en/Charts.php b/app/Language/en/Charts.php
index 5e3e8879d6e8707970fdf8c23b9db3e1c5089fa4..ac27218ebea87e31da682f9e80b78d5e0a664c5a 100644
--- a/app/Language/en/Charts.php
+++ b/app/Language/en/Charts.php
@@ -27,4 +27,6 @@ return [
     'by_domain_yearly' => 'Web pages visits by source (for the past year)',
     'by_entry_page' => 'Web pages visits by landing page (for the past week)',
     'podcast_bots' => 'Bots (crawlers)',
+    'daily_listening_time' => 'Daily cumulative listening time',
+    'monthly_listening_time' => 'Monthly cumulative listening time',
 ];
diff --git a/app/Language/en/PodcastNavigation.php b/app/Language/en/PodcastNavigation.php
index 9dae71b3c8349030facf852be33dc7509df839a1..262884081b421c20e7fb1de45b43b7ef1c6d7004 100644
--- a/app/Language/en/PodcastNavigation.php
+++ b/app/Language/en/PodcastNavigation.php
@@ -25,4 +25,5 @@ return [
     'podcast-analytics-locations' => 'Locations',
     'podcast-analytics-unique-listeners' => 'Unique listeners',
     'podcast-analytics-players' => 'Players',
+    'podcast-analytics-listening-time' => 'Listening time',
 ];
diff --git a/app/Language/fr/Breadcrumb.php b/app/Language/fr/Breadcrumb.php
index a27e5278fa0bd19bb2c3044079c9646b807191a8..50405e5375dcdeafcfb3eb570e623f06ec0048db 100644
--- a/app/Language/fr/Breadcrumb.php
+++ b/app/Language/fr/Breadcrumb.php
@@ -27,4 +27,5 @@ return [
     'webpages' => 'Pages web',
     'unique-listeners' => 'Auditeurs uniques',
     'players' => 'Lecteurs',
+    'listening-time' => 'Durée d’écoute',
 ];
diff --git a/app/Language/fr/Charts.php b/app/Language/fr/Charts.php
index 2469f6fbb2b92f318c2715519cd9d232a4dc6189..b878234d1056c1e1f26da65d6cf3543cee8de2c7 100644
--- a/app/Language/fr/Charts.php
+++ b/app/Language/fr/Charts.php
@@ -39,4 +39,6 @@ return [
     'by_entry_page' =>
         'Fréquentation des pages web par page d’entrée (sur la dernière semaine)',
     'podcast_bots' => 'Robots (bots)',
+    'daily_listening_time' => 'Durée quotidienne d’écoute cumulée',
+    'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée',
 ];
diff --git a/app/Language/fr/PodcastNavigation.php b/app/Language/fr/PodcastNavigation.php
index ea4838374670172f89dbb4d071b474dc28e751d7..9a2e09e93f5918e1281644ca1bc0258a77b6b427 100644
--- a/app/Language/fr/PodcastNavigation.php
+++ b/app/Language/fr/PodcastNavigation.php
@@ -25,4 +25,5 @@ return [
     'podcast-analytics-locations' => 'Localisations',
     'podcast-analytics-unique-listeners' => 'Auditeurs uniques',
     'podcast-analytics-players' => 'Lecteurs',
+    'podcast-analytics-listening-time' => 'Durée d’écoute',
 ];
diff --git a/app/Models/AnalyticsPodcastByEpisodeModel.php b/app/Models/AnalyticsPodcastByEpisodeModel.php
index fce1544546bf690aeecc7bec00ec1788b7355712..29506bda7eadcd821162fa65313ce31858f0177a 100644
--- a/app/Models/AnalyticsPodcastByEpisodeModel.php
+++ b/app/Models/AnalyticsPodcastByEpisodeModel.php
@@ -142,4 +142,71 @@ class AnalyticsPodcastByEpisodeModel extends Model
         }
         return $found;
     }
+
+    /**
+     * Gets listening-time data for a podcast
+     *
+     * @param int $podcastId
+     *
+     * @return array
+     */
+    public function getDataTotalListeningTimeByDay(int $podcastId): array
+    {
+        if (
+            !($found = cache(
+                "{$podcastId}_analytics_podcast_listening_time_by_day"
+            ))
+        ) {
+            $found = $this->select('date as labels')
+                ->selectSum('(enclosure_duration * hits)', 'values')
+                ->join('episodes', 'id = episode_id', 'inner')
+                ->where([
+                    $this->table . '.podcast_id' => $podcastId,
+                    'date >' => date('Y-m-d', strtotime('-60 days')),
+                ])
+                ->groupBy('labels')
+                ->orderBy('labels', 'ASC')
+                ->findAll();
+
+            cache()->save(
+                "{$podcastId}_analytics_podcast_listening_time_by_day",
+                $found,
+                600
+            );
+        }
+        return $found;
+    }
+
+    /**
+     * Gets listening-time data for a podcast
+     *
+     * @param int $podcastId
+     *
+     * @return array
+     */
+    public function getDataTotalListeningTimeByMonth(int $podcastId): array
+    {
+        if (
+            !($found = cache(
+                "{$podcastId}_analytics_podcast_listening_time_by_month"
+            ))
+        ) {
+            $found = $this->select('DATE_FORMAT(`date`,"%Y-%m-01") as `labels`')
+                ->selectSum('(enclosure_duration * hits)', 'values')
+                ->join('episodes', 'id = episode_id', 'inner')
+                ->where([
+                    $this->table . '.podcast_id' => $podcastId,
+                ])
+                ->groupBy('`labels`')
+                ->orderBy('`labels`', 'ASC')
+                ->findAll();
+
+            cache()->save(
+                "{$podcastId}_analytics_podcast_listening_time_by_month",
+                $found,
+                600
+            );
+        }
+        return $found;
+    }
 }
diff --git a/app/Models/AnalyticsPodcastByRegionModel.php b/app/Models/AnalyticsPodcastByRegionModel.php
index 45edbc2413d37e4b612a58b0e98e60d2fb60b51b..511a0769b17157296b3eb167abbbd5895b594180 100644
--- a/app/Models/AnalyticsPodcastByRegionModel.php
+++ b/app/Models/AnalyticsPodcastByRegionModel.php
@@ -38,13 +38,11 @@ class AnalyticsPodcastByRegionModel extends Model
                 "{$podcastId}_analytics_podcast_by_region_{$locale}"
             ))
         ) {
-            $found = $this->select(
-                '`country_code`, `region_code`, `latitude`, `longitude`'
-            )
+            $found = $this->select('`country_code`, `region_code`')
                 ->selectSum('`hits`', '`value`')
-                ->groupBy(
-                    '`country_code`, `region_code`, `latitude`, `longitude`'
-                )
+                ->selectAvg('`latitude`')
+                ->selectAvg('`longitude`')
+                ->groupBy('`country_code`, `region_code`')
                 ->where([
                     '`podcast_id`' => $podcastId,
                     '`date` >' => date('Y-m-d', strtotime('-1 week')),
diff --git a/app/Views/_assets/modules/Charts.ts b/app/Views/_assets/modules/Charts.ts
index b8d112077c2e683be49f4fdf443404c469c9ab1b..3266714ec47124637d0c39fbb3bb0c9e5ae1d04f 100644
--- a/app/Views/_assets/modules/Charts.ts
+++ b/app/Views/_assets/modules/Charts.ts
@@ -62,6 +62,41 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => {
   chart.scrollbarX = new am4core.Scrollbar();
 };
 
+const drawXYDurationChart = (chartDivId: string, dataUrl: string | null): void => {
+  // Create chart instance
+  const chart = am4core.create(chartDivId, am4charts.XYChart);
+  am4core.percent(100);
+  // Set theme
+  am4core.useTheme(am4themes_material);
+  // Create axes
+  const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
+  dateAxis.renderer.minGridDistance = 60;
+  const yAxis = chart.yAxes.push(new am4charts.DurationAxis());
+  yAxis.baseUnit = "second";
+  chart.durationFormatter.durationFormat = "hh'h,' mm'mn'";
+  // Add data
+  chart.dataSource.url = dataUrl || "";
+  chart.dataSource.parser.options.emptyAs = 0;
+  // Create series
+  const series = chart.series.push(new am4charts.LineSeries());
+  series.dataFields.valueY = "values";
+  series.dataFields.dateX = "labels";
+  series.tooltipText = "{valueY.formatDuration()}";
+  series.strokeWidth = 2;
+  // Make bullets grow on hover
+  const bullet = series.bullets.push(new am4charts.CircleBullet());
+  bullet.circle.strokeWidth = 2;
+  bullet.circle.radius = 4;
+  bullet.circle.fill = am4core.color("#fff");
+  const bullethover = bullet.states.create("hover");
+  bullethover.properties.scale = 1.3;
+  series.tooltip.pointerOrientation = "vertical";
+  chart.cursor = new am4charts.XYCursor();
+  chart.cursor.snapToSeries = series;
+  chart.cursor.xAxis = dateAxis;
+  chart.scrollbarX = new am4core.Scrollbar();
+};
+
 const drawXYSeriesChart = (
   chartDivId: string,
   dataUrl: string | null
@@ -152,6 +187,9 @@ const DrawCharts = (): void => {
       case "xy-chart":
         drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
         break;
+      case "xy-duration-chart":
+        drawXYDurationChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
+        break;
       case "xy-series-chart":
         drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
         break;
diff --git a/app/Views/admin/podcast/_sidebar.php b/app/Views/admin/podcast/_sidebar.php
index d62dd8573fc08e009422a74225371bc61abfb0ba..cf637465041739271b12909d856516ae721a2e5f 100644
--- a/app/Views/admin/podcast/_sidebar.php
+++ b/app/Views/admin/podcast/_sidebar.php
@@ -13,6 +13,7 @@ $podcastNavigation = [
         'items' => [
             'podcast-analytics',
             'podcast-analytics-unique-listeners',
+            'podcast-analytics-listening-time',
             'podcast-analytics-players',
             'podcast-analytics-locations',
             'podcast-analytics-webpages',
diff --git a/app/Views/admin/podcast/analytics/listening-time.php b/app/Views/admin/podcast/analytics/listening-time.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6cd05d0a26e828235db4f50f6ede82c6836ce4e
--- /dev/null
+++ b/app/Views/admin/podcast/analytics/listening-time.php
@@ -0,0 +1,34 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= $podcast->title ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('pageTitle') ?>
+<?= $podcast->title ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+
+<div class="mb-12 text-center">
+<h2><?= lang('Charts.daily_listening_time') ?></h2>
+<div class="chart-xy" id="by-day-listening-time-graph" data-chart-type="xy-duration-chart" data-chart-url="<?= route_to(
+    'analytics-data',
+    $podcast->id,
+    'PodcastByEpisode',
+    'TotalListeningTimeByDay'
+) ?>"></div>
+</div>
+
+<div class="mb-12 text-center">
+<h2><?= lang('Charts.monthly_listening_time') ?></h2>
+<div class="chart-xy" id="by-month-listening-time-graph" data-chart-type="xy-duration-chart" data-chart-url="<?= route_to(
+    'analytics-data',
+    $podcast->id,
+    'PodcastByEpisode',
+    'TotalListeningTimeByMonth'
+) ?>"></div>
+</div>
+
+<script src="/assets/charts.js" type="module"></script>
+<?= $this->endSection() ?>