My friend and I are working on a college project using .Net. We're trying to build a website that allows users to listen to audio clips in a similar manner to sites like Soundcloud. At the moment we have a basic MVC site that allows users to upload audio onto the server file system, and records various details about the file in a database table.
So now I'm trying to present the client with a list of available audio files and controls to play each file. I specifically want a different set of controls for each file; later in the project we intend to add waveform images for each file too.
After some research I found this stackoverflow post which helped me serve and play a single audio file from the server, however, I can't get this to work with multiple mp3s. In the above post the controller returns a file to the web page which is then played in an empty web page. I tested this in my controller, first using Server.PathMap, then returning the absolute path as a parameter of the File method as below:
public ActionResult Index()
{
var file = Server.MapPath("~/App_Data/Audio/09 - Supergrass - Cheapskate.mp3");
return File(@"c:\users\paul\documents\visual studio 2013\Projects\Web Applications Project\MVCTest2\App_Data\Audio\05. Debaser.mp3", "audio/mp3");
}
Both worked fine. I then considered how to do this with a list of files, but I've not had any luck yet.
My database table contains the following information about the audio files:
- a unique id (int)
- the file name
- the absolute file path
- a user id
The File method called in the example I found returns a FilePathResult to the client. I have tried to replace the absolute path column in the database with FilePathResult instead, but it doesn't work. I get an error saying The class 'System.Web.Mvc.FilePathResult' has no parameterless constructor
. Am I correct in thinking this error means I need to pass parameters into the FilePathResult as I call it from the database maybe?
In any case, I abandoned that course of action and looked for other ways to get the correct path into the element, but I haven't had any success.
I understand how it works in the other example, as it only has one audio result to return, so a simple call to Index() can return that single result. This is what it looks like with the players included, but they don't work, and when I inspect the elements, it's because it's pointing to the absolute paths:
Here's what the markup looks like:
<tr>
<td>
02 - Supergrass - Richard III.mp3
</td>
<td>
<article class="audio">
<audio controls>
<source src="/AudioClip/http%3a/localhost/50279/App_Data/Audio/02%20-%20Supergrass%20-%20Richard%20III.mp3" type="audio/mp3" />
<p>Your browser does not support HTML 5 audio element</p>
</audio>
</article>
</td>
<td>
http://localhost/50279/App_Data/Audio/02 - Supergrass - Richard III.mp3
</td>
<td>
1
</td>
<td>
0
</td>
<td>
<a href="/AudioClip/Edit/20">Edit</a> |
<a href="/AudioClip/Details/20">Details</a> |
<a href="/AudioClip/Delete/20">Delete</a>
</td>
</tr>
Here's the relevant parts of my AudioClipController:
namespace MVCTest2.Controllers
{
public class AudioClipController : Controller
{
//
// GET: /AudioClip/
string audioFilePath = "~/App_Data/Audio";
AudioDb _db = new AudioDb();
public ActionResult Index(string searchTerm = null)
{
var model =
_db.AudioClips
.OrderBy(r => r.Title)
.Where(r => searchTerm == null || r.Title.StartsWith(searchTerm))
.Take(10)
.Select(r => r);
return View(model);
}
//
// GET: /AudioClip/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /AudioClip/Create
[HttpGet]
public ActionResult Create()
{
return View("Create");
}
//
// POST: /AudioClip/Create
[HttpPost]
public ActionResult Create(HttpPostedFileBase file) //FormCollection collection
{
// Verify that the user selected a file
if (file != null && file.ContentLength > 0)
{
// extract only the fielname
var fileName = Path.GetFileName(file.FileName);
// store the file inside ~/App_Data/uploads folder
var path = Path.Combine(Server.MapPath(audioFilePath), fileName);
file.SaveAs(path);
AudioClip audioClip = new AudioClip(fileName, path, 1, 0);
_db.AudioClips.Add(audioClip);
_db.SaveChanges();
return RedirectToAction("INDEX", new { Id = audioClip.AudioClipId });
}
// redirect back to the index action to show the form once again
return RedirectToAction("Index");
}
My AudioClip model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MVCTest2.Models
{
public class AudioClip
{
[Display(Name="ID")]
public int AudioClipId { get; set; }
[Display(Name = "Clip Title")]
public string Title { get; set; }
[Display(Name = "File Path")]
public string FilePath { get;set; }
[Display(Name = "User Name")]
public int BloggerId { get; set; }
[Display(Name = "Linked To")]
public int MasterId { get; set; }
// the virtual keyword issues a second query to solve null
// pointer at AudioClip.Comments. There are multiple ways to do this
// including more efficent ones, see:
// Loading Related Entities: http://msdn.microsoft.com/en-US/data/jj574232
public virtual ICollection<Comment> Comments { get; set; }
public AudioClip() {
}
public AudioClip(string title, string filePath, int bloggerId, int masterId) {
Title = title;
FilePath = filePath;
BloggerId = bloggerId;
MasterId = masterId;
}
}
}
And my AudioClip Index view:
@model IEnumerable<MVCTest2.Models.AudioClip>
@{
ViewBag.Title = "Index";
}
<h2>Audio Clips</h2>
<form method="get">
<input type="search" name="searchTerm" />
<input type="submit" value="Search by Name" />
</form>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.FilePath)
</th>
<th>
@Html.DisplayNameFor(model => model.BloggerId)
</th>
<th>
@Html.DisplayNameFor(model => model.MasterId)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
<article class="audio">
<audio controls>
<source src="@Url.Action(item.FilePath)" type="audio/mp3" />
<p>Your browser does not support HTML 5 audio element</p>
</audio>
</article>
</td>
<td>
@Html.DisplayFor(modelItem => item.FilePath)
</td>
<td>
@Html.DisplayFor(modelItem => item.BloggerId)
</td>
<td>
@Html.DisplayFor(modelItem => item.MasterId)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AudioClipId }) |
@Html.ActionLink("Details", "Details", new { id=item.AudioClipId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AudioClipId })
</td>
</tr>
}
</table>
I'm thinking I must be able to add this information to the AudioClip model and the database in order to allow me to generate multiple audio players that play different files, but right now I can't see it. Can any of you point me in the right direction?