0

Is there a way I can force an MP3 file to download from Amazon S3.

I have a Download button in my Razor:

 <td>
                <a href="@t.S3PreSignedUrl" class="js_recordingDownloadButton document-link btn btn-info btn-block br2 btn-xs fs12 @Html.Raw(t.S3PreSignedUrl.IsNullOrWhiteSpace() ? "disabled" : "")" target="_blank" data-container="body" data-toggle="tooltip" title="@t.OriginalFilename" type="@t.MimeType" download>
                    <span class="fa fa-cloud-download fs12"></span>
                </a>
            </td>

Currently, when you click on it, another browser window is opened and starts to play automatically using a Modal:

<div id="js_PlayRecordingPopup" class="popup-basic mfp-with-anim modalPopup">
    <div class="panel">
        <div class="admin-form">
            <div class="panel-heading">
                <span class="panel-title">
                    <i class="fa fa-play"></i> Play Recording
                </span>
            </div>
        </div>

        <div class="panel-body bt0 text-center p25">

            <p class="popupInfo fs12 mb5">Playing: <b class="text-info js_playingTitle"></b></p>
            <p class="popupInfo fs12">Filename: <b class="text-info js_playingFileName"></b></p>

            <div class="summaryBox popupSummary text-center audioContainerBox">
                <audio controls controlsList="nodownload" id="audRecording">
                    Your browser does not support the audio element.
                </audio>
            </div>

        </div>

        <div class="panel-footer">
            <div class="text-center">
                <input type="button" class="btn btn-primary" value="Done" data-bind="click: function(){ var sound = document.getElementById('audRecording'); if(sound != undefined) { sound.pause(); sound.currentTime = 0; } $.magnificPopup.close(); }">
            </div>
        </div>
    </div>
    <button title="Close (Esc)" type="button" class="mfp-close" data-bind="click: function(){ var sound = document.getElementById('audRecording'); if(sound != undefined) { sound.pause(); sound.currentTime = 0; }}">×</button>
</div>

Is there a way I can set it so that if I click Download it downloads the file straight away?

This is what it looks like in the Source Code:

<td>
                    <a href="https:url/Audio/Recordings/TES/39e7ca51-1ac8-f395-3ae6-ff814dbde6c3/39e7ca51-e77c-65f1-c88e-47fe20f67ee1/o_1cj5s0tntp3aa1011o51tlrd8ba.mp3?X-Amz-Expires=86400&amp;response-content-disposition=inline%3B%20filename%3Drain-01.mp3&amp;X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=AKIAIOHIWJAIQSFECYZQ/20180724/eu-west-1/s3/aws4_request&amp;X-Amz-Date=20180724T104420Z&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Signature=02a5febff28eed31646a37fea0b8da7d7bcf4b0ffe9a3365d31a0ac3f0b2cabb" class="js_recordingDownloadButton document-link btn btn-info btn-block br2 btn-xs fs12 " target="_blank" data-container="body" data-toggle="tooltip" title="rain-01.mp3" type="audio/mp3" download>
                        <span class="fa fa-cloud-download fs12"></span>
                    </a>
                </td>
Rob
  • 153
  • 18
  • Asked over and over again. No, you cannot change what the browser decides to do with a file download. – Camilo Terevinto Jul 24 '18 at 10:41
  • Really, so I cannot have it so that it forces a download? – Rob Jul 24 '18 at 10:42
  • No, not in a user-friendly way (you could say that the file type is unknown for a terrible user experience). It would be a security problem if you could – Camilo Terevinto Jul 24 '18 at 10:45
  • Okay thank you, so I am best leaving it as it is, where it opens the file in a new tab, and plays the file. If you right-click on the player then you can save it if you wish. – Rob Jul 24 '18 at 10:47
  • Have a look for ContentDisposition (in System.Net.Mime), it's a http header which lets the browser know how to treat the return value, however, the browser is entirely free to ignore it and handle the file however it likes. – Davesoft Jul 24 '18 at 10:55
  • @Davesoft that is already being used, but I am going to see if I remove the play modal and target blank and see if it works. – Rob Jul 24 '18 at 10:56

2 Answers2

1
  1. You can change Content-Type on the response to Content-Type: application/octet-stream. While also setting Content-Disposition: attachment; filename="filename.mp3". Make sure filename uses encoding defined in RFC 5987

  2. I see you already have found the download attribute on HTML5 but you're not supplying a filename. It should be used like so:

<a href="pathtofile.mp3" download="filename">

  1. You could always test this download.js written by dandavis. If that works you could reverse his code.
Community
  • 1
  • 1
Riddell
  • 1,429
  • 11
  • 22
  • I have checked the HTTP Header when you click on Download it opens in a new tab and the HTML5 player starts automatically. There is this in the header: `Content-Disposition: inline; filename=rain-01.mp3` – Rob Jul 24 '18 at 12:42
  • Also on the `download` you dont technically need to supply `download="filename" because when you click it the browser loads up a html5 player similar to this: `https://codepen.io/anilbms/embed/IAvkj` – Rob Jul 24 '18 at 13:13
  • https://stackoverflow.com/questions/19046718/aws-s3-force-file-download-using-response-content-disposition – Riddell Jul 24 '18 at 13:21
1

I worked through a solution using S3 behind CloudFront this weekend (August 2022); and, since Amazon has recently updated CloudFront functions to allow for this fairly easily, I wanted to share how I accomplished a solution, in case someone else may need it as you work through the Amazon Documentation.

How it works

  • Example Listen Link:

    https://d111111abcdef8.cloudfront.net/audio.mp3

    (plays in browser)

  • Example Download Link:

    https://d111111abcdef8.cloudfront.net/audio.mp3?title=Custom%20Title%20for%20File

    (forces download of the .mp3 file with the custom filename: Custom Title for Downloaded File.mp3)

My Solution:

  1. Here is my custom CloudFront function's code, which you can use as an example to help you write what you need:
function handler(event) {
    //This is a viewer response function.
    
    var request = event.request;
    var uri = request.uri;
    var qs = request.querystring;
    //console.log ('qs: ' + qs);
    
    var response = event.response;
    var headers = response.headers;
    
    if(!qs.title) {
        //console.log('No- qs title');
    } else{
        var title = qs.title.value;
        title = decodeURIComponent(title);
        title = title.split('+').join(' ');
        //console.log('Yes- qs title LEN: ' + title.length);
        
        if (uri.endsWith('.mp3')) {
            var fileType = '.mp3';
            var fileName = title + fileType;
            //console.log('fileName: ' + fileName);
            var disposition = "attachment; filename=" + fileName;
            //console.log('disposition:' + disposition);
            
            headers['content-disposition'] = { value: disposition };
        }
    }
   
    return response;
}
  1. I set up my CloudFront Distribution to have a new behavior with:
  • Path pattern: "*"
  • Origin: my S3 bucket
  • Cache Policy to look for and accept only the "title" key in uri query string
  • Origin Request Policy to also look for and accept only the "title" key in uri query string
  • Associated my custom CloudFront function (above) as a Viewer response function type
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Holy crap - there are so many questions on SO about how to deal with `attachment` vs `inline` for serving S3 files from Cloudfront - this is the ONLY one that solves the problem without using "signed URLs". This is a fantastic solution. I actually set up a second CF distribution to apply this behaviour to as CF functions have a small cost to run, so I will use the 2nd distribution when putting "download specific" links on my site. – zombat Feb 22 '23 at 19:44