歌曲播放与下载
从网站首页、歌曲排行榜和歌曲搜索得知,每个页面的歌曲信息都设置了歌曲播放的地址链接,只要在页面上单击歌曲名即可访问歌曲播放页。总的来说,歌曲播放页是音乐网站的核心页面,所有页面的歌曲信息都有播放链接,通过播放链接而已访问可以访问歌曲播放页。
定义路由
歌曲播放由项目应用play实现,在play的urls.py中分别定义路由play和download。
路由的定义过程如下:
python
from django.urls import path
from .views import *
urlpatterns = [
# 歌曲播放页
path('<int:id>.html', playView, name='play'),
# 歌曲下载
path('download/<int:id>.html', downloadView, name='download')
]
视图定义
路由play和download设置路由变量id,该变量是模型Song的主键id,主要用于标记和区分当前播放的歌曲信息。路由play为用户提供在线试听、歌曲下载、歌曲点评链接和相关歌曲推荐,路由download用于实现歌曲下载功能。在play的view.py中分别定义视图函数playView和downloadView。
代码如下:
python
from django.shortcuts import render
from django.http import StreamingHttpResponse
from index.models import *
def playView(request, id):
# 热搜歌曲
searchs = Dynamic.objects.select_related('song').order_by('-search').all()[:6]
# 相关歌曲推荐
type = Song.objects.values('type').get(id=id)['type']
relevant = Dynamic.objects.select_related('song').filter(song__type=type).order_by('-plays').all()[:6]
# 歌曲信息
songs = Song.objects.get(id=int(id))
# 播放列表
play_list = request.session.get('play_list', [])
exist = False
if play_list:
for i in play_list:
if int(id) == i['id']:
exist = True
if exist == False:
play_list.append({'id': int(id), 'singer': songs.singer, 'name': songs.name, 'time': songs.time})
request.session['play_list'] = play_list
# 歌词
if songs.lyrics != '暂无歌词':
lyrics = str(songs.lyrics.url)[1::]
with open(lyrics, 'r', encoding='utf-8') as f:
lyrics = f.read()
# 添加播放次数
# 功能扩展:可使用Session实现每天只添加一次播放次数
p = Dynamic.objects.filter(song_id=int(id)).first()
plays = p.plays + 1 if p else 1
Dynamic.objects.update_or_create(song_id=id, defaults={'plays': plays})
return render(request, 'play.html', locals())
def downloadView(request, id):
# 添加下载次数
p = Dynamic.objects.filter(song_id=int(id)).first()
download = p.download + 1 if p else 1
Dynamic.objects.update_or_create(song_id=id, defaults={'download': download})
# 读取文件内容
# 根据id查找歌曲信息
songs = Song.objects.get(id=int(id))
file = songs.file.url[1::]
def file_iterator(file, chunk_size=512):
with open(file, 'rb') as f:
while True:
c = f.read(chunk_size)
if c:
yield c
else:
break
# 将文件内容写入StreamingHttpResponse对象
# 并以字节流方式返回给用户,实现文件下载
f = str(id) + '.m4a'
response = StreamingHttpResponse(file_iterator(file))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment; filename="%s"' %(f)
return response
视图函数playView分别实现4次数据查询、播放列表的设置、歌词的读取和播放次数的累加,功能的实现过程说明如下:
(1)变量searchs实现歌曲搜索框下方的热搜歌曲,变量relevant实现相关歌曲推荐功能,将同一类型的歌曲展示在歌曲播放页的最下方。
(2)播放列表由会话Session存储当前用户的播放记录。
(3)变量songs获取当前歌曲的信息,如果当前歌曲存在歌词文件,就读取歌词文件的数据内容,并以变量lyrics表示。
(4)累加播放次数用于查询模型Dynamic是否存在歌曲动态信息,若存在,则将播放次数累加1,否则新增动态信息并将播放次数设为1,最后调用内置方法update_or_create实现动态信息的更新或新增操作。如果模型Dynamic不存在当前歌曲的动态信息,那么内置方法update_or_create将执行数据新增操作,否则执行数据更新操作。
视图函数downloadView实现歌曲文件的下载功能,歌曲每下载一次,就对歌曲的下载次数累加1,因此,视图函数downloadView实现两个功能;累计下载次数和文件下载,功能说明如下:
(1)累加下载次数与累加播放次数的功能相似,两者都是调用Django内置方法update_or_create实现动态信息的更新或新增操作,前者是操作模型字段download,后者是操作模型字段plays。
(2)文件下载使用StreamingHttpResponse实现,这是实现流式响应输出(流式响应输出是使用Python的迭代器将数据进行分段处理并传输)。
模板定义
核心就是音乐的播放部分
bash
<audio id="myAudio" controls autoplay>
<source src="{{ song.file.url }}" type="audio/mpeg">
</audio>
下载可以通过a标签
ini
<a href="/user/test/xxxx.txt" download="文件名.txt">点击下载</a>
<button class="down">下载</button>
但是有个情况,比如txt、png、jpg等这些浏览器支持直接打开的文件是不会执行下载任务的,而是会直接打开文件,这个时候就需要给a标签添加一个属性"download"。
代码借鉴
xml
{% extends "base.html" %}
{% load static %}
{% block link %}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/common.css" %}">
<link rel="stylesheet" href="{% static "css/play.css" %}">
{% endblock %}
{# ① #}
{% block body %}
<body>
<div class="header">
<a href="/" class="logo"><img src="{% static "image/logo.png" %}"></a>
<div class="search-box">
<!-- 歌曲搜索框 -->
<form id="searchForm" action="{% url 'search' 1 %}" method="post">
{% csrf_token %}
<div class="search-keyword">
<input id="kword" name="kword" type="text" class="keyword" maxlength="120">
</div>
<input id="subSerch" type="submit" class="search-button" value="搜 索" />
</form>
<div id="suggest" class="search-suggest"></div>
<div class="search-hot-words">
{% for s in searchs %}
<a target="play" href="{% url 'play' s.song.id %}" >{{ s.song.name }}</a>
{% endfor %}
</div>
</div>
</div><!--end header-->
<div class="nav-box">
<div class="nav-box-inner">
<ul class="nav clearfix">
<li><a href="{% url 'index' %}">首页</a></li>
<li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
<li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
</ul>
</div>
</div><!--end nav-box-->
<div class="wrapper clearfix">
<div class="content">
{# ② #}
<div class="product-detail-box clearfix">
<div class="product-pics">
<div class="music_box">
<div id="jquery_jplayer_1" class="jp-jplayer" data-url={{ songs.file.url }}></div>
<div class="jp_img layz_load pic_po" title="点击播放"><img data-src={{ songs.img.url }}></div>
<div id="jp_container_1" class="jp-audio">
<div class="jp-gui jp-interface">
<div class="jp-time-holder clearfix">
<div class="jp-progress">
<div class="jp-seek-bar">
<div class="jp-play-bar"></div>
</div>
</div>
<div class="jp-time">
<span class="jp-current-time"></span> /
<span class="jp-duration"></span>
</div>
</div>
<div class="song_error_corr" id="songCorr">
<b class="err_btn">纠错</b>
<ul>
<li><span>歌词文本错误</span></li>
<li><span>歌词时间错误</span></li>
<li><span>歌曲错误</span></li>
</ul>
</div>
<div class="jp-volume-bar">
<div class="jp-volume-bar-value"></div>
</div>
<ul class="jp-controls clearfix">
<li>
<a class="jp-play" tabindex="1" title="play"></a>
<a class="jp-pause" tabindex="1" title="pause"></a>
</li>
<li>
<a class="jp-stop" tabindex="1" title="stop"></a>
</li>
<li>
<a class="jp-repeat" tabindex="1" title="repeat"></a>
<a class="jp-repeat-off" tabindex="1" title="repeat off"></a>
</li>
<li class="sound">
<a class="jp-mute" tabindex="1" title="mute"></a>
<a class="jp-unmute" tabindex="1" title="unmute"></a>
</li>
</ul>
</div>
</div>
<div class="jplayer_content">
<ul id="lrc_list" class="lrc_list"></ul>
</div>
</div><!--end music_box-->
<textarea id="lrc_content" style="display: none;">
{{ lyrics }}
</textarea>
</div><!--end product-pics-->
<div class="product-detail-main">
<div class="product-price">
<h1 id="currentSong" >{{ songs.name }}</h1>
<div class="product-price-info">
<span>歌手:{{ songs.singer }}</span>
</div>
<div class="product-price-info">
<span>专辑:{{ songs.album }}</span>
<span>语种:{{ songs.languages }}</span>
</div>
<div class="product-price-info">
<span>流派:{{ songs.type }}</span>
<span>发行时间:{{ songs.release }}</span>
</div>
</div><!--end product-price-->
<div class="product-comment">
<div class="links clearfix">
<a class="minimum-link-A click_down" href="{% url 'download' songs.id %}">下载</a>
<a class="minimum-link-A" href="{% url 'comment' songs.id %}" >歌曲点评</a>
</div><!-- end links-->
{# ③ #}
<h3 class="list_title">当前播放列表</h3>
<ul class="playing-li" id="songlist">
<!--播放列表-->
{% for item in play_list %}
{%if item.id == songs.id %}
<li data-id="{{item.id}}" class="current">
{%else %}
<li data-id="{{item.id}}">
{%endif %}
<span class="num">{{forloop.counter}}</span>
<a class="name" href="{% url 'play' item.id %}" target="play" >{{item.name}}</a>
<a class="singer" href="javascript:;" target="_blank" >{{item.singer}}</a>
</li>
{% endfor %}
</ul>
<div class="nplayL-btns" id="playleixin">
<ul>
<li class="order current" data-run="order">
<a class="icon" href="javascript:void(0)" title="顺序播放"></a>
</li>
<li class="single" data-run="single">
<a class="icon" title="单曲循环" href="javascript:void(0)"></a>
</li>
<li class="random" data-run="random">
<a class="icon" title="随机播放" href="javascript:void(0)"></a>
</li>
<li class="next" data-run="next">
<a href="javascript:void(0)"><i></i>播放下一首</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<h3>相关歌曲</h3>
</div>
<div class="section-content">
<div class="parts-box">
<a href="javascript:;" target="_self" id="J_PartsPrev" class="prev-btn"><i></i></a>
<div class="parts-slider" id="J_PartsList">
<div class="parts-list-wrap f_w">
<ul id="" class="parts-list clearfix f_s">
{% for item in relevant %}
<li>
{% if item.song.id != songs.id %}
<a class="pic layz_load pic_po" href="{% url 'play' item.song.id %}" target="play" >
<img data-src="{{ item.song.img.url }}">
</a>
<h4><a href="{% url 'play' item.song.id %}" target="play" >{{ item.song.name}}</a></h4>
<a href="javascript:;" class="J_MoreParts accessories-more">{{ item.song.singer }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
<a href="javascript:;" target="_self" id="J_PartsNext" class="next-btn"><i></i></a>
</div>
</div>
</div>
</div>
</div>
<script data-main="{% static "js/play.js" %}" src="{% static "js/require.js" %}"></script>
</body>
{% endblock %}
我们将模板文件play.html实现的功能划分为3部分,在上述代码中已标注。
(1)标注①与之前的功能相同,不再重复讲解
(2)标注②实现了歌曲播放、歌曲信息、歌曲下载与歌曲点评。歌曲播放功能在class="jp-jplayer"的div标签里实现,标签属性data-url设置歌曲文件的路径地址,由JavaScript播放歌曲文件;歌曲信息在class="product-detail-main"的div标签里实现,而歌词动态效果在textarea文本框实现,歌曲的播放进度与歌词的滑动效果由JavaScript实现,歌曲下载与歌曲点评在class="links clearfix"的div标签中分别设置路由download和comment。
(3)标注③实现歌曲的播放列表和相关歌曲列表。歌曲的播放列表遍历模板上下文play_list生成数据列表,相关歌曲列表遍历模板上下文relevant生成相关歌曲列表。
歌曲点评
在歌曲播放页设置了歌曲点评的地址链接,单击"点评"按钮即可访问当前歌曲的点评页面。歌曲点评页实现两个功能:歌曲点评和歌曲点评信息列表,说明如下:
(1)歌曲点评是为用户提供歌曲点评功能,以表单的形式实现数据提交。
(2)歌曲点评信息列表是根据当前歌曲信息查找模型Comment的点评数据,并以数据列表的形式展示在网页。
定义路由
我们在项目应用comment中实现歌曲点评页,打开comment的urls.py定义歌曲点评的路由信息,代码如下:
javascript
from django.urls import path
from .views import *
urlpatterns = [
path('<int:id>.html', commentView, name='comment'),
]
定义视图
路由comment设置路由变量id,他代表模型Song的主键id,用于区分和识别当前歌曲的点评信息。路由的HTTP请求由视图函数commentView负责接收和处理,在comment的views.py中定义视图函数commentView,代码如下:
python
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from django.shortcuts import render, redirect
from django.shortcuts import reverse
from django.http import Http404
from index.models import *
import time
def commentView(request, id):
# 热搜歌曲
searchs = Dynamic.objects.select_related('song').order_by('-search').all()[:6]
# 点评内容的提交功能
if request.method == 'POST':
text = request.POST.get('comment', '')
# 如果用户处于登录状态,则使用用户名,反之使用匿名用户
if request.user.username:
user = request.user.username
else:
user = '匿名用户'
now = time.strftime('%Y-%m-%d', time.localtime(time.time()))
if text:
comment = Comment()
comment.text = text
comment.user = user
comment.date = now
comment.song_id = id
comment.save()
return redirect(reverse('comment', kwargs={'id': str(id)}))
else:
songs = Song.objects.filter(id=id).first()
# 歌曲不存在抛出404异常
if not songs:
raise Http404('歌曲不存在')
c = Comment.objects.filter(song_id=id).order_by('date')
page = int(request.GET.get('page', 1))
paginator = Paginator(c, 2)
try:
pages = paginator.page(page)
except PageNotAnInteger:
pages = paginator.page(1)
except EmptyPage:
pages = paginator.page(paginator.num_pages)
return render(request, 'comment.html', locals())
视图函数commentView分别对GET请求和POST请求执行不同的处理,并且将路由变量id作为函数参数id。当我们从歌曲播放页进入歌曲点评页,浏览器访问歌曲点评页,相当于向网站发送GET请求,视图函数commentView执行以下处理:
(1)视图函数commentView将函数参数id作为模型Song的查询条件,如果模型Song没有记录当前歌曲,就抛出404异常并提示歌曲不存在。
(2)如果模型Song存在当前歌曲,就将函数参数id作为模型Comment的查询条件,查询当前歌曲的点评信息,查询结果命名为c。然后从GET请求中获取请求参数page,并且生成变量page,如果请求参数page不存在,变量page的值就设为1。
(3)最后将变量page和查询结果c进行分页处理,生成分页对象pages,并调用模板文件comment.html生成歌曲点评页。
如果我们在歌曲点评页填写点评内容并单击"发布"按钮,浏览器就向网站发送POST请求,视图函数commentView将会接收一个POST请求,并执行以下处理:
(1)从网页表单中获取点评内容,并将点评内容命名为变量text。然后获取当前用户名,如果当前用户处于未登录状态,那么用户名设为匿名用户,并且以变量user表示。
(2)如果变量text不为空,就在模型Comment中新增一条点评信息,分别记录点评内容、用户名和点评日期,模型Comment的外键字段song设为函数参数id,这是为当前歌曲新增一条点评信息。
(3)最后重定向歌曲点评页,以GET请求方式访问歌曲点评页,将新增的点评信息显示在歌曲点评页,网站的重定向可以防止表单多次提交,解决同一条点评信息重复创建的问题。
定义模型
下一步在模板文件comment.html中编写相应的网页内容。实现5个功能:歌曲搜索框、网站导航栏功能、歌曲点评框、歌曲点评信息列表和分页导航功能。
xml
{% extends "base.html" %}
{% load static %}
{% block link %}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/common.css" %}">
<link rel="stylesheet" href="{% static "css/comment.css" %}">
{% endblock %}
{% block body %}
<body class="review">
<div class="header">
<a href="/" class="logo"><img src="{% static "image/logo.png" %}"></a>
<div class="search-box">
<form id="searchForm" action="{% url 'search' 1 %}" method="post">
{% csrf_token %}
<div class="search-keyword">
<input id="kword" name="kword" type="text" class="keyword" maxlength="120">
</div>
<input id="subSerch" type="submit" class="search-button" value="搜 索"/>
</form>
<div id="suggest" class="search-suggest"></div>
<div class="search-hot-words">
{% for s in searchs %}
<a target="play" href="{% url 'play' s.song.id %}" >{{ s.song.name }}</a>
{% endfor %}
</div>
</div>
</div><!--end header-->
<div class="nav-box">
<div class="nav-box-inner">
<ul class="nav clearfix">
<li><a href="{% url 'index' %}">首页</a></li>
<li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
<li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
</ul>
</div>
</div><!--end nav-box-->
<div class="wrapper">
<div class="breadcrumb">
<a href="/">首页</a> >
<a href="{% url 'play' id %}" target="_self">{{songs.name}}</a> >
<span>点评</span>
</div>
<div class="page-title" id="currentSong"></div>
</div>
<div class="wrapper">
<div class="section">
<div class="section-header"><h3 class="section-title">网友点评</h3></div>
<div class="section-content comments-score-new review-comments-score clearfix">
<div class="clearfix">
<!--点评框-->
<div class="comments-box">
<div class="comments-box-title">我要点评<<{{ songs.name }}>></div>
<div class="comments-default-score clearfix"></div>
<form action="" method="post" id="usrform">
{% csrf_token %}
<div class="writebox">
<textarea name="comment" form="usrform"></textarea>
</div>
<div class="comments-box-button clearfix">
<input type="submit" value="发布" class="_j_cc_post_entry cc-post-entry" id="scoreBtn">
<div data-role="user-login" class="_j_cc_post_login"></div>
</div>
<div id="scoreTips2" style="padding-top:10px;"></div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="wrapper clearfix">
<div class="content">
<div id="J_CommentList">
<ul class="comment-list">
{% for item in pages.object_list %}
<li class="comment-item ">
<div class="comments-user">
<span class="face">
<img src="{% static "image/user.jpg" %}" width="60" height="60">
</span>
</div>
<div class="comments-list-content">
<div class="single-score clearfix">
<span class="date">{{ item.date }}</span>
<div><span class="score">{{ item.user }}</span></div>
</div>
<!--comments-content-->
<div class="comments-content">
<div class="J_CommentContent comment-height-limit">
<div class="content-inner">
<div class="comments-words">
<p>{{ item.text }}</p>
</div>
</div>
</div>
</div>
</div>
</li>
{% endfor %}
</ul>
<div class="page-box">
<div class="pagebar" id="pageBar">
{% if pages.has_previous %}
<a href="{% url 'comment' id %}?page={{ pages.previous_page_number }}" class="prev" target="_self"><i></i>上一页</a>
{% endif %}
{% for page in pages.paginator.page_range %}
{% if pages.number == page %}
<span class="sel">{{ page }}</span>
{% else %}
<a href="{% url 'comment' id %}?page={{ page }}" target="_self">{{ page }}</a>
{% endif %}
{% endfor %}
{% if pages.has_next %}
<a href="{% url 'comment' id %}?page={{ pages.next_page_number }}" class="next" target="_self"><i></i>下一页</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</body>
{% endblock %}